前言
在之前我们学到了集合映射,但是集合映射有一个明显的缺陷,就是不能满足多的一方保存多个字段多个属性的数据,这就需要我们学习集合中保存一个对象,然后在配置中采取多对一和一对多的映射关系,把对象和表之间的关系对应起来,满足我们的需要。在实际的开发中,多对一和一对多的映射应用非常广泛,也是每个学习Hibernate框架的同学必须掌握的内容。本文主要通过部门与员工的案例来展开。
部门与员工案例
首先要清楚部门与员工的关系
一个部门对应多个员工;一个员工只能属于一个部门。
即一个部门维护了多个员工信息,而一个员工只对应一个部门。
设计
用一张图来说明今天的重点内容。
从上图中,实体主要有部门和员工,一个实体Dept维护了集合Set,多个员工的数据,而一个员工维护了一个对象Dept,从数据库设计来说,员工表里面有一个外键关联到部门ID,因此在映射文件中必须关联起来。
代码部分:
部门实体类:Dept.java
public class Dept {
private int deptId;
private String deptName;
// 【一对多关系维护】 部门对应的多个员工
private Set<Employee> emps = new HashSet<Employee>();
public int getDeptId() {
return deptId;
}
public void setDeptId(int deptId) {
this.deptId = deptId;
}
public String getDeptName() {
return deptName;
}
public void setDeptName(String deptName) {
this.deptName = deptName;
}
public Set<Employee> getEmps() {
return emps;
}
public void setEmps(Set<Employee> emps) {
this.emps = emps;
}
}
部门映射文件一对多,一方配置Dept.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.nwpu.geeker_one2Many">
<class name="Dept" table="t_dept">
<id name="deptId">
<generator class="native"></generator>
</id>
<property name="deptName" length="20"></property>
<!--
一对多关联映射配置 (通过部门维护到员工)
Dept 映射关键点:
1. 指定 映射的集合属性: "emps"
2. 集合属性对应的集合表: "t_employee"
3. 集合表的外键字段 "t_employee. dept_id"
4. 集合元素的类型
-->
<set name="emps" table="t_employee" >
<key column="dept_id"></key>
<one-to-many class="Employee"/>
</set>
</class>
</hibernate-mapping>
需要注意:映射文件中,一方部门维护多方员工在javaBean和映射文件中必须设置集合,必须有集合属性名称,指定的外键字段,和集合保存的元素类型,javaBean.这样部门才能关联到员工。因为通过部门映射文件在加载时会去找Dept.java中定义的集合名称,然后在设值的时候set集合,必须要清楚集合中保存的类型是什么,关联的表是什么,这就需要集合元素类型定义好,然后才能找到对应的员工类,加载员工类的实体映射关系配置文件,找到员工表。
员工实体类Employee.java多方
public class Employee {
private int empId;
private String empName;
private double salary;
// 【多对一关系】员工与部门
private Dept dept;
public int getEmpId() {
return empId;
}
public void setEmpId(int empId) {
this.empId = empId;
}
public String getEmpName() {
return empName;
}
public void setEmpName(String empName) {
this.empName = empName;
}
public double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
public Dept getDept() {
return dept;
}
public void setDept(Dept dept) {
this.dept = dept;
}
}
映射文件多方Employee.hbm.xml
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.nwpu.geeker.one2Many">
<class name="Employee" table="t_employee">
<id name="empId">
<generator class="native"></generator>
</id>
<property name="empName" length="20"></property>
<property name="salary" type="double"></property>
<!--
多对一映射配置
Employee 映射关键点:
1. 映射的部门属性 : dept
2. 映射的部门属性,对应的外键字段: dept_id
3. 部门的类型
-->
<many-to-one name="dept" column="dept_id" class="Dept"></many-to-one>
</class>
</hibernate-mapping>
注意:多方维护一方,必须有many-to-one。员工要想维护部门,必须知道部门的实体类型,还有关联的外键字段,在通过员工给部门设值的时候还要知道员工实体类中部门的成员变量的名称(dept)。
测试保存:App.java
public class App1_save {
private static SessionFactory sf;
static {
sf = new Configuration()
.configure()
.addClass(Dept.class)
.addClass(Employee.class) // 测试时候使用
.buildSessionFactory();
}
// 保存, 部门方 【一的一方法操作】
@Test
public void save() {
Session session = sf.openSession();
session.beginTransaction();
// 部门对象
Dept dept = new Dept();
dept.setDeptName("应用开发部");
// 员工对象
Employee emp_zs = new Employee();
emp_zs.setEmpName("张三");
Employee emp_ls = new Employee();
emp_ls.setEmpName("李四");
// 关系
dept.getEmps().add(emp_zs);
dept.getEmps().add(emp_ls);
// 保存
session.save(emp_zs);
session.save(emp_ls);
session.save(dept); // 保存部门,部门下所有的员工
session.getTransaction().commit();
session.close();
/*
* 结果
* Hibernate: insert into t_employee (empName, salary, dept_id) values (?, ?, ?)
Hibernate: insert into t_employee (empName, salary, dept_id) values (?, ?, ?)
Hibernate: insert into t_dept (deptName) values (?)
Hibernate: update t_employee set deptId=? where empId=? 维护员工引用的部门的id
Hibernate: update t_employee set deptId=? where empId=?
*/
}
// 【推荐】 保存, 部员方 【多的一方法操作】
@Test
public void save2() {
Session session = sf.openSession();
session.beginTransaction();
// 部门对象
Dept dept = new Dept();
dept.setDeptName("综合部");
// 员工对象
Employee emp_zs = new Employee();
emp_zs.setEmpName("张三");
Employee emp_ls = new Employee();
emp_ls.setEmpName("李四");
// 关系
emp_zs.setDept(dept);
emp_ls.setDept(dept);
// 保存
session.save(dept); // 先保存一的方法
session.save(emp_zs);
session.save(emp_ls);// 再保存多的一方,关系回自动维护(映射配置完)
session.getTransaction().commit();
session.close();
/*
* 结果
* Hibernate: insert into t_dept (deptName) values (?)
Hibernate: insert into t_employee (empName, salary, dept_id) values (?, ?, ?)
Hibernate: insert into t_employee (empName, salary, dept_id) values (?, ?, ?)
少生成2条update sql
*/
}
}
注意:经过测试,上面save1方法和save2方法都能保存数据关联到数据库中。但是数据库执行语句,save2明显少了两条sql语句。原因是让多的一方(员工)去set,去维护一的一方(部门),然后保存的时候先保存部门信息,再保存员工信息,这样因为部门有了, 部门的ID有了,员工信息保存时就能直接插入信息+外键到员工表中,不用再update外键字段了。
解释save1方法为什么会多两条sql语句?
原因是上面的save1方法,部门维护员工,即一方维护了多方。即员工先保存,然后再保存部门,那么在员工表中的外键关联部门ID字段并不知道关联的是谁,因此,在部门信息保存之后,还要进行updata员工表中的外键字段。
总结:
如何一个对象生成一个外键字段?
通过<many-to-one>
配置。这个很重要,一般都要配置的。
在一对多与多对一的关联关系中,保存数据最好的通过多的一方来维护关系(在员工对象中设置部门属性),这样可以减少update语句的生成,从而提高hibernate的执行效率!因为jdbc数据库操作明显要比Hibernate效率高,所以提高Hibernate效率也是关键。
在映射关系中,我们可以配置双方的配置文件映射关系,当然也可以只配置一方来维护关系。
如果一对多与多对一都要配置维护关系,这种叫“双向关联”
如果只配置一对多, 叫“单向一对多”
只配置多对一, 叫“单向多对一”
注意:
配置了哪一方,哪一方才有维护关联关系的权限!即配置了哪一方,就得必须用哪一方的对象来set另一方的属性值。