Hibernate 03-配置关联关系映射

朱雀 2022-02-25 11:16 331阅读 0赞

一、了解关联关系

  • 类与类之间最普遍的关系就是关联关系,并且关联是有方向的。以部门(Dept)和员工(Emp)类为例,一个部门下有多个员工,而一个员工只能属于一个部门。从Emp——->Dept是多对一关联,这就意味着每个Emp对象只会引用一个Dept对象;而从Dept—>Emp是一对多关联,这就意味着每个Dept对象会引用一组Emp对象。因此,在Emp类中应该定义一个Dept类型的属性,来引用所关联的Dept对象;而在Dept类中应该定义一个集合类型的属性,来引用所有关联的Emp对象。
  • 如果仅有从Emp到Dept的关联,或者仅有从Dept到Emp的关联,就称为单向关联;如果同时包含两种关联,就称为双向关联。

二、建立单向多对一关联关系

关键步骤:

  • 编写Dept和Emp持久化类并配置映射文件。
  • 使用<many-to-one>元素建立Emp的外键DEPTNO和dept属性之间的映射。
  • 验证单向多对一关联关系对象持久化方法。

(1)编写Dept和Emp持久化类并配置映射文件

Dept.java

  1. /**
  2. * 部门表
  3. */
  4. public class Dept implements Serializable {
  5. /**
  6. * 部门编号
  7. */
  8. private Integer deptNo;
  9. /**
  10. * 部门名称
  11. */
  12. private String dName;
  13. /**
  14. * 部门地区
  15. */
  16. private String loc;
  17. //省略多个getter/setter方法
  18. }

Dept.hbm.xml

  1. <!DOCTYPE hibernate-mapping PUBLIC
  2. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  3. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  4. <hibernate-mapping>
  5. <class name="cn.demo.po.Dept" table="DEPT" schema="scott" dynamic-update="true">
  6. <id name="deptNo" type="java.lang.Integer" column="DEPTNO">
  7. <generator class="assigned"/>
  8. </id>
  9. <property name="dName" type="java.lang.String" column="DNAME"/>
  10. <property name="loc" type="java.lang.String">
  11. <column name="LOC"></column>
  12. </property>
  13. </class>
  14. </hibernate-mapping>

Emp.java:建立单向多对一的关系,故要在该实体类中添加Dept对象属性

  1. /**
  2. * 员工类
  3. */
  4. public class Emp {
  5. /**
  6. * 员工编号
  7. */
  8. private Integer empNo;
  9. /**
  10. * 员工姓名
  11. */
  12. private String ename;
  13. /**
  14. * 员工职位
  15. */
  16. private String job;
  17. /**
  18. * 上级编号
  19. */
  20. private Integer mgr;
  21. /**
  22. * 入职日期
  23. */
  24. private Date hiredate;
  25. /**
  26. * 工资
  27. */
  28. private Double sal;
  29. /**
  30. * 福利
  31. */
  32. private Double comm;
  33. /**
  34. * 部门编号
  35. */
  36. private Integer deptNo;
  37. /**
  38. * 员工所属部门
  39. */
  40. private Dept dept;
  41. //省略多个getter/setter方法
  42. }

(2)使用<many-to-one>元素建立Emp的外键DEPTNO和dept属性之间的映射

Emp.hbm.xml

  1. <!DOCTYPE hibernate-mapping PUBLIC
  2. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  3. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  4. <hibernate-mapping>
  5. <class name="cn.demo.po.Emp" table="EMP" schema="scott" dynamic-update="true">
  6. <id name="empNo" type="java.lang.Integer" column="EMPNO">
  7. <generator class="increment"/>
  8. </id>
  9. <property name="ename" type="java.lang.String" column="ENAME"/>
  10. <!--<property name="deptNo" type="java.lang.Integer" column="DEPTNO"/>-->
  11. <property name="mgr" type="java.lang.Integer" column="MGR"/>
  12. <property name="job" type="java.lang.String" column="JOB"/>
  13. <property name="sal" type="java.lang.Double" column="SAL" />
  14. <property name="comm" type="java.lang.Double" column="COMM" />
  15. <property name="hiredate" type="java.util.Date" column="HIREDATE"/>
  16. <!--建立Emp与Dept的多对一关联关系-->
  17. <many-to-one name="dept" column="DEPTNO" class="cn.demo.po.Dept"/>
  18. </class>
  19. </hibernate-mapping>

说明:

【1】<many-to-one>元素建立了Emp表的外键DEPTNO和dept属性之间的映射。

属性如下:

  • name:设定持久化类的属性名,此处为Emp类的dept属性。
  • column:设定持久化类的属性对应表的外键,此处为Emp表的外键DEPTNO。
  • class:设定持久化类的属性的类型,此处设定dept属性为Dept类型。
  • 此处做多对一映射无需再使用以下语句,不然会报异常:重复映射列

(3)验证单向多对一关联关系对象持久化方法

【1】添加或修改Emp对象,外键信息封装在Dept对象中,需建立Emp对象和Dept对象的关联,最后保存或更新Emp对象。

EmpDao.java

  1. /**
  2. * 添加Emp
  3. * @param emp
  4. */
  5. public void save(Emp emp){
  6. this.currentSession().save(emp);
  7. }

EmpBiz.java

  1. /**
  2. * 添加Emp
  3. * @param emp
  4. */
  5. public void addNewEmp(Emp emp){
  6. Transaction tx=null;
  7. try {
  8. tx=empDao.currentSession().beginTransaction(); //开启事务
  9. empDao.save(emp);
  10. tx.commit();
  11. }catch (HibernateException e){
  12. e.printStackTrace();
  13. if(tx!=null){
  14. tx.rollback();
  15. }
  16. }
  17. }

测试类

  1. /**
  2. * 测试添加Emp员工信息方法
  3. */
  4. @Test
  5. public void testAddNewEmp(){
  6. //创建Emp对象
  7. Emp emp=new Emp();
  8. emp.setEname("李四");
  9. //指定员工所在的部门为会计部门
  10. Dept dept=new Dept();
  11. dept.setDeptNo(10);
  12. emp.setDept(dept);
  13. //保存员工数据
  14. new EmpBiz().addNewEmp(emp);
  15. }

【2】按照指定的Dept对象来查询相关的Emp对象

EmpDao.java

  1. /**
  2. * 根据部门编号获取员工列表
  3. * @param dept
  4. * @return
  5. */
  6. public List<Emp> findByDept(Dept dept){
  7. String hql="from Emp where dept=?";
  8. return currentSession().createQuery(hql).setParameter(0,dept).list();
  9. }

EmpBiz.java

  1. /**
  2. * 根据部门编号获取Emp列表
  3. * @param dept
  4. * @return
  5. */
  6. public List<Emp> findEmpsByDept(Dept dept){
  7. Transaction tx=null;
  8. List<Emp> result=null;
  9. try {
  10. tx=empDao.currentSession().beginTransaction();
  11. result=empDao.findByDept(dept);
  12. tx.commit();
  13. }catch (HibernateException e){
  14. e.printStackTrace();
  15. if(tx!=null){
  16. tx.rollback();
  17. }
  18. }
  19. return result;
  20. }

测试类

  1. /**
  2. * 测试根据部门编号获取Emp列表
  3. */
  4. @Test
  5. public void testFindEmpsByDept(){
  6. Dept dept=new Dept();
  7. dept.setDeptNo(10);
  8. List<Emp> empList=new EmpBiz().findEmpsByDept(dept);
  9. for(Emp emp:empList){
  10. System.out.println("员工姓名:"+emp.getEname()+",部门名称:"+emp.getDept().getdName());
  11. }
  12. }

【3】输出指定Emp集合中所有的Emp对象及其关联的Dept对象的信息

EmpDao.java

  1. /**
  2. * 查询所有员工:使用Query对象的list()方法
  3. * @return
  4. */
  5. public List<Emp> findAll(){
  6. //构建Query对象
  7. Query query=currentSession().createQuery("from Emp");
  8. return query.list();
  9. }

EmpBiz.java

  1. /**
  2. * 获取所有员工列表
  3. * @return
  4. */
  5. public List<Emp> findAllEmpList(){
  6. Transaction tx=null;
  7. List<Emp> result=null;
  8. try {
  9. tx=empDao.currentSession().beginTransaction();
  10. result=empDao.findAll();
  11. for(Emp emp:result){
  12. System.out.print("员工姓名:"+emp.getEname()+"\t");
  13. System.out.println("所在部门 :"+emp.getDept().getdName()+"\t");
  14. }
  15. tx.commit();
  16. }catch (HibernateException e){
  17. e.printStackTrace();
  18. if(tx!=null){
  19. tx.rollback();
  20. }
  21. }
  22. return result;
  23. }

测试类

  1. /**
  2. * 测试获取所有员工列表及相应的部门信息
  3. */
  4. @Test
  5. public void testFindAllEmpList(){
  6. List<Emp> empList=new EmpBiz().findAllEmpList();
  7. }

三、建立双向一对多关联关系

关键步骤如下:

  • 在Dept类中增加一个集合类型的emps属性
  • 使用<set>元素映射emps属性
  • 验证双向一对多关联关系对象持久化方法

(1)在Dept类中增加一个集合类型的emps属性

  1. public class Dept{
  2. private Set<Emp> emps=new HashSet<Emp>(); //部门所包含的员工
  3. public Set<Emp> getEmps() {
  4. return emps;
  5. }
  6. public void setEmps(Set<Emp> emps) {
  7. this.emps = emps;
  8. }
  9. //省略其他属性
  10. }

(2)使用<set>元素映射emps属性

Dept.hbm.xml

  1. <!DOCTYPE hibernate-mapping PUBLIC
  2. "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
  3. "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
  4. <hibernate-mapping>
  5. <class name="cn.demo.po.Dept" table="DEPT" schema="scott" dynamic-update="true">
  6. <id name="deptNo" type="java.lang.Integer" column="DEPTNO">
  7. <generator class="increment"/>
  8. </id>
  9. <property name="dName" type="java.lang.String" column="DNAME"/>
  10. <property name="loc" type="java.lang.String">
  11. <column name="LOC"></column>
  12. </property>
  13. <!--映射Dept与Emp双向一对多关联关系-->
  14. <set name="emps">
  15. <key column="DEPTNO"/>
  16. <one-to-many class="cn.demo.po.Emp"/>
  17. </set>
  18. </class>
  19. </hibernate-mapping>

说明:

【1】<set>元素的name属性:设定持久化类的属性名,此处为Dept类的emps属性。
【2】<set>元素还包含另外两个子元素:

  • <key>子元素:column属性设定与所关联的持久化类相对应的表的外键,此处为Emp表中的DEPTNO字段。
  • <one-to-many>子元素:表示emps集合中存放的是一组Emp对象。

(3)验证双向一对多关联关系对象持久化方法

基于关联关系除了可以通过对象间导航实现相关对象的自动检索外,还可以在
对象的增删改操作中,对相关对象实现自动化的级联处理,而无需进行相关编码,从而减少编码工作量,提高开发效率。级联操作的细节可以在持久化类的映射文件通过cascade属性和inverse属性进行控制。

(1)cascade属性

  • Dept类、Emp类及其映射文件编写完成后,从Dept到Emp的双向一对多的关联已经建立。
  • 在对象-关系映射文件中,用于映射持久化类之间关联关系的元素,如<set><many-to-one>都有一个cascade属性,它用于指定如何操纵与当前对象关联的其他对象。

cascade属性的部分常用可选值如下:

  • (1)none(cascade属性默认值):当Session操纵当前对象时,忽略其他关联的对象。
  • (2)save-update:当通过Session的save()、update()及saveOrUpdate()方法来保存或更新
    当前对象时,级联保存所有关联的瞬时状态的对象,并且级联更新所有关联的游离状态的对象。
  • (3)delete:当通过Session的delete()方法删除当前对象时,会级联删除所有关联的对象。
  • (4)all:包含save-update、delete的行为。

【例1:使用cascade属性进行级联添加操作】

将Dept.hbm.xml中设置元素的cascade属性为save-update,表示Hibernate在
持久化Dept对象时,会自动持久化关联的所有Emp对象,语句如下:

  1. <set name="emps" cascade="save-update">...</set>

DeptDao.java

  1. /**
  2. * 新增部门信息
  3. * @param dept
  4. */
  5. public void save(Dept dept){
  6. this.currentSession().save(dept); //保存指定的Dept对象
  7. }

DeptBiz.java

  1. /**
  2. * 新增部门
  3. * @param dept
  4. */
  5. public void addNewDept(Dept dept){
  6. Transaction tx=null;
  7. try {
  8. tx=deptDao.currentSession().beginTransaction(); //开启事务
  9. deptDao.save(dept);
  10. tx.commit();
  11. }catch (HibernateException e){
  12. e.printStackTrace();
  13. if(tx!=null){
  14. tx.rollback();
  15. }
  16. }
  17. }

测试类

  1. /**
  2. * 测试使用cascade属性级联添加Dept操作
  3. */
  4. @Test
  5. public void testAddNewDept(){
  6. //创建一个Dept对象和一个Emp对象
  7. Dept dept=new Dept(22,"质控部","中部");
  8. Emp emp=new Emp();
  9. emp.setEname("王五");
  10. //建立Dept对象与Emp对象的双向关联关系
  11. emp.setDept(dept);
  12. dept.getEmps().add(emp);
  13. //保存Dept对象
  14. new DeptBiz().addNewDept(dept);
  15. }

运行结果:
cascade属性的值设为“save-update”,Hibernate在持久化Dept对象时,会自动持久化关联的所有Emp对象。Hibernate会执行以下sql语句:
在这里插入图片描述
语句 “update emp set DEPTNO=? WHERE empno=?” 用来保证级联添加的EMP记录的外键字段能够正确指向相关的DEPT记录。

【例2:使用cascade属性级联删除操作】

修改Dept.hbm.xml元素cascade属性为delete,语句如下

  1. <set name="emps" cascade="delete">...</set>

DeptDao.java

  1. /**
  2. * 根据id获取Dept对象:使用session.load()方式
  3. * @param id
  4. * @return Dept对象
  5. */
  6. public Dept load(Serializable id){
  7. //通过Session的load()方法根据OID加载指定对象
  8. return (Dept)currentSession().load(Dept.class,id);
  9. }
  10. /**
  11. * 设置cascade属性删除部门
  12. * @param dept
  13. */
  14. public void deleteDept(Dept dept){
  15. this.currentSession().delete(this.load(dept.getDeptNo()));
  16. }

DeptBiz.java

  1. /**
  2. * 设置casecade属性delete,级联删除Dept
  3. * @param dept
  4. */
  5. public void deleteDept(Dept dept){
  6. Transaction tx=null;
  7. try {
  8. tx=deptDao.currentSession().beginTransaction();
  9. deptDao.deleteDept(dept);
  10. tx.commit();
  11. }catch (HibernateException e){
  12. e.printStackTrace();
  13. if(tx!=null){
  14. tx.rollback();
  15. }
  16. }
  17. }

测试类

  1. /**
  2. * 测试级联删除Dept对象的同时删除Emp对象
  3. */
  4. @Test
  5. public void testDeleteDept(){
  6. //封装待删除的Dept对象
  7. Dept dept=new Dept();
  8. dept.setDeptNo(41);
  9. //删除该Dept对象
  10. new DeptBiz().deleteDept(dept);
  11. }

运行结果:删除Dept对象的之前,将与其关联的Emp对象全部删除

(2)<set>元素的inverse属性

  • “inverse”直译为”反转”,在Hibernate中,inverse属性指定了关联关系中的方向。
  • <set>元素的inverse属性的取值有两个:即true和false,默认false。
  • 关联关系中,inverse=”false”的一方为主动方,主动方会负责维护关联关系。

【例1:修改指定员工的部门信息】

分别为EmpDao.java和DeptDao.java创建load()方法

  1. /**
  2. * 获取指定Emp对象
  3. * @param empNo 员工编号
  4. * @return
  5. */
  6. public Emp load(Serializable empNo){
  7. return (Emp) currentSession().load(Emp.class,empNo);
  8. }
  9. /**
  10. * 获取Dept对象
  11. * @param 部门编号
  12. * @return
  13. */
  14. public Dept load(Serializable id){
  15. //通过Session的get()方法根据OID加载指定对象
  16. return (Dept)currentSession().load(Dept.class,id);
  17. }

EmpBiz.java

  1. /**
  2. * 修改员工所属部门
  3. * @param empNo 员工编号
  4. * @param deptNo 部门编号
  5. */
  6. public void changeDept(Integer empNo,Integer deptNo){
  7. Transaction tx=null;
  8. try {
  9. tx=empDao.currentSession().beginTransaction();
  10. //加载Emp和Dept的持久化对象
  11. Dept dept=new DeptDao().load(deptNo);
  12. Emp emp=empDao.load(empNo);
  13. //建立Dept对象和Emp对象的关联关系
  14. emp.setDept(dept); //Hibernate检查到持久化对象emp的属性发生改变后,在进行commit时会执行一次update语句
  15. dept.getEmps().add(emp); //持久化对象dept上的属性emps发生改变,会再次执行一次update语句,故共执行两次
  16. //提交事务
  17. tx.commit();
  18. }catch (HibernateException e) {
  19. e.printStackTrace();
  20. if (tx != null) {
  21. tx.rollback();
  22. }
  23. }
  24. }

测试类

  1. /**
  2. * 测试更改员工部门信息
  3. */
  4. @Test
  5. public void testChangeDept(){
  6. new EmpBiz().changeDept(7369,41);
  7. }

运行结果
在这里插入图片描述
从运行结果可以看出:修改成功,但是update语句执行了两次。

说明:
Hibernate会按照持久化对象的属性变化来同步更新数据库。如果Dept.hbm.xml文件中的<set>元素的inverse属性值设置为false(或不设置,其默认值false),那么Hibernate将重复执行 两次 以下关键SQL语句。

  1. update scott.EMP set DEPTNO=? where EMPNO=?

以上SQL语句表明Hibernate执行了两次update操作。Hibernate根据内存中持久化对象的属性变化来决定需要执行哪些SQL语句。在以上demo中,建立Emp对象和Dept对象的双向关联关系时,分别进行了如下修改:

  • (1)修改Emp对象,建立Emp对象到Dept对象的多对一关联关系。

    emp.setDept(dept);

Hibernate检查到持久化对象emp的属性发生变化后,会执行相应的sql语句。

  • (2)修改Dept对象,建立Dept对象到Emp对象的一对多关联关系。

    dept.getEmps.add(emp);

因为Dept.hbm.xml文件中元素的inverse属性值为false,Dept一方会主动维护关联关系,所以Hibernate检查到持久化对象dept属性的上述变化后,会执行相应的修改sql语句:update scott.EMP set DEPTNO=? where EMPNO=?

但是从代码中分析,Emp对象已经通过emp.setDept(dept)设定了正确的关联关系,故dept.getEmps.add(emp)语句实际上是多余的,执行多余的update语句会影响应用的性能。因此在这种情况下可以把<set>元素的inverse属性值设置为true。

  1. <set name="emps" cascade="all" inverse="true"></set>

以上配置表明在Dept和Emp的双向关联关系中,Dept端的关联只是Emp端关联的镜像。Hibernate仅按照Emp对象的关联属性的变化来同步更新数据库,而忽略Dept关联属性的变化。再次执行程序,Hibernate将仅执行一条关键更新语句。

在这里插入图片描述
对代码再次进行修改,进一步验证Dept.hbm.xml中配置了inverse=“true后,Hibernate不会通过Dept对象的设置,而仅通过Emp对象来维护二者间的关联关系。

  • (1)修改EmpBiz.java,不建立Dept到Emp对象的关联

    public void changeDept(Integer empNo,Integer deptNo){

    1. Transaction tx=null;
    2. try {
    3. tx=empDao.currentSession().beginTransaction();
    4. //加载Emp和Dept的持久化对象
    5. Dept dept=new DeptDao().load(deptNo);
    6. Emp emp=empDao.load(empNo);
    7. //仅建立Emp对象和Dept对象的关联关系
    8. emp.setDept(dept);
    9. //提交事务
    10. tx.commit();
    11. }catch (HibernateException e) {
    12. e.printStackTrace();
    13. if (tx != null) {
    14. tx.rollback();
    15. }
    16. }
    17. }
  • (2)修改EmpBiz.java,不建立Emp到Dept对象的关联

    public void changeDept(Integer empNo,Integer deptNo){

    1. Transaction tx=null;
    2. try {
    3. tx=empDao.currentSession().beginTransaction();
    4. //加载Emp和Dept的持久化对象
    5. Dept dept=new DeptDao().load(deptNo);
    6. Emp emp=empDao.load(empNo);
    7. //建立Dept对象和Emp对象的关联关系
    8. dept.getEmps().add(emp);
    9. //提交事务
    10. tx.commit();
    11. }catch (HibernateException e) {
    12. e.printStackTrace();
    13. if (tx != null) {
    14. tx.rollback();
    15. }
    16. }
    17. }

说明: 以上代码仅设置了Dept对象的emp属性,由于元素的inverse属性值为true,Hibernate没有执行任何update语句,也就意味着Emp表中该条员工记录的外键没有得到正确更新,由此可得到以下操作建议:

  • 当映射双向一对多的关联关系时,在“一”方把元素的inverse属性设置为true,可以提高应用的性能。
  • 在代码中建立两个对象到的关联时,应该同时修改关联两端对象的相关属性。

    emp.setDept(dept);

    1. dept.getEmps().add(emp);

这样才能使程序更加健壮,提高业务逻辑层的独立性,使业务逻辑层的代码不受Hibernate实现的影响。同时,当解除双向关联的关系时,也应该同时修改关联两端的对象的相应属性,如下:

  1. emp.setDept(null);
  2. dept.getEmps().remove(emp);

四、建立多对多关联关系

在这里插入图片描述

1、配置单向多对多关联关系

Project.java

  1. /**
  2. * 项目类
  3. */
  4. public class Project implements Serializable {
  5. //项目编号
  6. private Integer proid;
  7. //项目名称
  8. private String proName;
  9. //参与项目的多个员工
  10. private Set<Employee> employees=new HashSet<Employee>(0);
  11. //省略getter/setter方法
  12. }

Employee.java

  1. /**
  2. * 员工类
  3. */
  4. public class Employee implements Serializable {
  5. /**
  6. * 员工编号
  7. */
  8. private Integer empId;
  9. /**
  10. * 员工姓名
  11. */
  12. private String empName;
  13. }

Project.hbm.xml配置如下:

  1. <hibernate-mapping>
  2. <class name="cn.demo.po.Project" table="`PROJECT`" schema="scott" dynamic-update="true">
  3. <id name="proid" column="PROID" type="java.lang.Integer"/>
  4. <property name="proName" column="PRONAME" type="java.lang.String"/>
  5. <!--映射Proect-Employee多对多关联关系-->
  6. <set name="employees" table="PROEMP" cascade="save-update">
  7. <!--ProEmp表的项目id-->
  8. <key column="RPROID"/>
  9. <!--集合类型为Employee-->
  10. <many-to-many class="cn.demo.po.Employee" column="REMPID"/>
  11. </set>
  12. </class>
  13. </hibernate-mapping>

说明:

  • (1)元素的table属性指定关系表的名称为PROEMP
  • (2)元素的cascade属性为“save-update”,表明保存或更新Project对象时,会级联保存或更新与它关联的Employee对象。
  • (3)元素的子元素指定PROEMP的外键RPROID,用来参照Project表
  • (4)子元素的class属性指定employees集合中存放的是Employee对象,
    column属性指定PROEMP表的外键REMPID,用来参照Employee表。

注:对于多对多关联,cascade属性设置为”save-update”是合理的,但是不建议把cascade属性设为“all”“delete”。如果删除一个Project对象则级联删除与它关联的的所有Employee对象,由于这些Employee对象还有可能与其他Project对象关联,因此当Hibernate执行级联删除时,会破坏数据库的外键参照完整性。

ProjectDao.java

  1. /**
  2. * 添加项目
  3. * @param project
  4. */
  5. public void save(Project project){
  6. this.currentSession().save(project);
  7. }

ProjectBiz.java

  1. /**
  2. * 添加项目
  3. * @param project
  4. */
  5. public void addNewProject(Project project){
  6. Transaction tx=null;
  7. try {
  8. tx=projectDao.currentSession().beginTransaction();
  9. projectDao.save(project);
  10. tx.commit();
  11. }catch (HibernateException e){
  12. e.printStackTrace();
  13. if(tx!=null){
  14. tx.rollback();
  15. }
  16. }
  17. }

测试类

  1. /**
  2. * 测试添加项目
  3. */
  4. @Test
  5. public void testAddNewProject(){
  6. //创建两个员工对象
  7. Employee employee1=new Employee(1,"张三");
  8. Employee employee2=new Employee(2,"李四");
  9. //创建两个项目对象
  10. Project project1=new Project(1,"1号项目");
  11. Project project2=new Project(2,"2号项目");
  12. //将项目1分别与两个员工进行关联
  13. project1.getEmployees().add(employee1);
  14. project1.getEmployees().add(employee2);
  15. //将项目2与employee1进行关联
  16. project2.getEmployees().add(employee1);
  17. //进行持久化操作
  18. projectBiz.addNewProject(project1);
  19. projectBiz.addNewProject(project2);
  20. }

说明:

  • (1)当Session的save()方法保存project1对象时,向PROJECT表插入一条记录,同时还会分别向EMPLOYEE和PROEMP表插入两条记录。
  • (2)当Session的save()方法保存project2对象时,向PROJECT插入一条记录,同时向PROEMP表插入一条记录。由于与project2对象关联的employee1对象已经被保存到数据库中,因此不再向EMPLOYEE表插入记录。
  • (3)注意主键生成策略,不能为increment,只能为assigned。

2、配置双向多对多关联关系

在Employee.java中添加属性projects

  1. /**
  2. * 项目集合
  3. */
  4. private Set<Project> projects=new HashSet<Project>();
  5. public Set<Project> getProjects() {
  6. return projects;
  7. }
  8. public void setProjects(Set<Project> projects) {
  9. this.projects = projects;
  10. }

Project.hbm.xml不做更改,配置多对多关联关系

  1. <hibernate-mapping>
  2. <class name="cn.demo.po.Project" table="PROJECT1" schema="scott">
  3. <id name="proId" column="`PROID`" type="java.lang.Integer">
  4. <generator class="assigned"/>
  5. </id>
  6. <property name="proName" column="`PRONAME`" type="java.lang.String"/>
  7. <!--映射Proect-Employee多对多关联关系-->
  8. <set name="employees" table="PROEMP" cascade="save-update">
  9. <!--ProEmp表的项目id-->
  10. <key column="RPROID"/>
  11. <!--集合类型为Employee-->
  12. <many-to-many class="cn.demo.po.Employee" column="REMPID"/>
  13. </set>
  14. </class>
  15. </hibernate-mapping>

在Employee.hbm.xml中配置多对多关联关系

  1. <hibernate-mapping>
  2. <class name="cn.demo.po.Employee" table="EMPLOYEE" schema="scott">
  3. <id name="empId" column="EMPID" type="java.lang.Integer">
  4. <generator class="assigned"/>
  5. </id>
  6. <property name="empName" column="EMPNAME" type="java.lang.String"/>
  7. <!--员工与项目之间的多对多关联关系-->
  8. <set name="projects" table="PROEMP" inverse="true">
  9. <key column="REMPID"/>
  10. <!--指定projects集合类型-->
  11. <many-to-many class="cn.demo.po.Project" column="RPROID"/>
  12. </set>
  13. </class>
  14. </hibernate-mapping>

Dao层Service层不做修改,测试类如下

  1. /**
  2. * 测试添加项目:双向多对多关系
  3. */
  4. @Test
  5. public void testAddNewProject2(){
  6. //创建两个员工对象
  7. Employee employee1=new Employee(1,"张三");
  8. Employee employee2=new Employee(2,"李四");
  9. //创建两个项目对象
  10. Project project1=new Project(1,"1号项目");
  11. Project project2=new Project(2,"2号项目");
  12. //建立项目到员工的关系
  13. project1.getEmployees().add(employee1);
  14. project1.getEmployees().add(employee2);
  15. project2.getEmployees().add(employee1);
  16. //建立员工到项目的关系
  17. employee1.getProjects().add(project1);
  18. employee1.getProjects().add(project2);
  19. employee2.getProjects().add(project1);
  20. //进行添加操作
  21. projectBiz.addNewProject(project1);
  22. projectBiz.addNewProject(project2);
  23. }

注:在实际开发中,如果连接表中除了两个外键,还包括其他业务字段,那么根据业务需要,可以把多对多关联分解为两个一对多关联。

五、配置查询加载策略

通过关联关系可以在程序中方便的获取关联对象的数据,但是如果从数据库中加载Dept对象,会同时自动加载所有关联的Emp对象,而程序实际上仅仅需要访问Dept对象,那么这些关联的Emp对象就白白浪费了许多空间。当Hibernate查询Dept对象时,立即查询并加载与之关联的Emp对象,这种查询策略称为立即加载。立即加载存在两大不足:

  • (1)会执行不必要的查询语句,影响查询性能。
  • (2)可能会加载大量不需要的对象,增加系统开销,浪费内存空间。

为了解决以上问题,Hibernate提供了延迟加载策略。
延迟加载策略能避免加载应用程序不需要访问的关联对象。

Hibernate允许在对象-关系映射文件中使用lazy属性配置加载策略,并且可以分为类级和关联级两个级别分别进行控制。lazy属性常用取值如下:

  • (1)类级别:元素中lazy的属性可选值为true(延迟加载)和false(立即加载),默认true。
  • (2)一对多关联级别:元素中lazy属性的可选值为true(延迟加载)、extra(增强延迟加载)和false(立即加载),默认true。
  • (3)多对一关联级别:元素中lazy属性的可选值为proxy(延迟加载)、no-proxy(无代理延迟加载)和false(立即加载)。默认proxy。

说明:由此可看出,对于Hibernate3.x以上的版本,无论哪个级别,默认采用的都是延迟加载的查询策略,以减少系统资源的开销。

1、配置类级别的查询加载策略

类级别可选的加载策略包括立即加载和延迟加载,默认为延迟加载。如果元素的lazy属性为true,表示采用延迟加载;如果lazy属性为false,表示采用立即加载。

(1)立即加载
例:

  1. <class name="cn.demo.po.Dept" lazy="false" table="DEPT">...</class>

说明:当通过Session的load()方法加载Dept对象时,执行语句后:

  1. Dept dept=(Dept)session.load(Dept.class,10);

Hibernate会立即执行查询Dept表中的select语句:

  1. select * from dept where deptno=?

(2)延迟加载

类级别的默认加载策略是延迟加载,以下两种方式都表示采用延迟加载策略:

  1. <class name="cn.demo.po.Dept" table="DEPT"></class>

  1. <class name="cn.demo.po.Dept" lazy="true" table="DEPT"></class>

说明:如果程序加载一个持久化对象的目的是访问它的属性,则可以采用立即加载。如果程序加载一个持久化对象的目的仅仅是获得它的引用,则可以采用延迟加载。

范例:

  1. @Test
  2. public void testAddNewEmp(){
  3. Transaction tx=null;
  4. try {
  5. tx=empDao.currentSession().beginTransaction();
  6. //使用session.load方法延迟加载dept对象
  7. Dept dept=(Dept) empDao.currentSession().load(Dept.class,10);
  8. Emp emp=new Emp();
  9. emp.setEname("TOM");
  10. emp.setDept(dept);
  11. empDao.currentSession().save(emp); //调用Session的save方法进行保存
  12. tx.commit();
  13. }catch (HibernateException e){
  14. e.printStackTrace();
  15. if(tx!=null){
  16. tx.rollback();
  17. }
  18. }
  19. }

说明: 因为Dept类级别采用延迟加载,session.load()方法不会执行访问Dept表的select语句,只会返回一个Dept代理类的实例,它的deptNo属性为10,其余属性都为null。以上代码仅由session.save()方法执行一条insert语句:

  1. insert into scott.EMP (ENAME, MGR, JOB, SAL, COMM, HIREDATE,
  2. DEPTNO, EMPNO)
  3. values (?, ?, ?, ?, ?, ?, ?, ?)

当元素的lazy属性设置为true时,会影响Session的load()方法的各种运行时行为。
下面举例说明:

  • (1)通过load()方法加载的延迟状态的Dept代理实例,除了OID,其他属性均为null。通过调用其getDeptName()等方法可以促使Hibernate执行查询,获得数据从而完成该代理实例的初始化。
  • (2)调用代理类实例的getDeptNo()方法访问OID属性,不会触发Hibernate初始化代理类实例的行为,而是直接返回Dept代理类实例的OID值,无需查询数据库。
  • (3)org.hibernate.Hibernate类的 initialize() 静态方法用于显式初始化代理类实例,isInitialized()方法用于判断代理类实例是否已经被初始化。以下代码通过Hibernate类的initialize()方法显式初始化了Dept代理类实例。

    tx=session.beginTransaction();

    1. Dept dept=(Dept)session.load(Dept.class,10);
    2. //判断是否已经初始化,若没有则显式初始化
    3. if(!Hibernate.isInitialized(dept)){
    4. Hibernate.initialize(dept);
    5. }
  • (4)如果加载的Dept代理实例的OID在数据库中不存在,Session的load()方法不会立即抛出异常,因为此时并未真正执行查询。只有当hibernate试图完成对Dept代理实例的初始化时,才会真正执行查询语句,这时会抛出以下异常:

    org.hibernate.ObjectNotFoundException:No row with the given identifier exists

  • (5)Dept代理类的实例只有在当前Session范围内才能被初始化。如果在当前Session的生命周期内,应用程序没有完成Dept代理实例的初始化工作,那么在当前Session关闭后,试图访问该Dept代理实例中OID以外的属性(如调用getDeptName()),将抛出以下异常:

    org.hibernate.LazyInitializationException:could not initialize proxy-no session

注意: 无论Dept.hbm.xml文件的<class>元素的lazy属性是true还是false,Session的get()方法及Query对象的list()方法在类级别总是使用立即加载策略,
举例说明如下:

【1】当通过Session的get()方法加载Dept对象时:

  1. tx=session.beginTransaction();
  2. Dept dept=(Dept)session.get(Dept.class,10);
  3. tx.commit();

Hibernate会立即执行以下select语句:select * from dept where deptno=?
如果存在相关的数据,get()方法就会返回Dept对象,否则就返回null。
get()方法永远不会返回Dept代理类实例,这是它与load()方法的区别之一。

【2】当运行Query对象的list()方法时:

  1. tx=session.beginTransaction();
  2. List deptList=session.createQuery("from Dept").list();
  3. tx.commit();

Hibernate会立即执行以下select语句:select * from dept

2、配置一对多和多对多关联的查询加载策略

在映射文件中,用元素的lazy属性来配置一对多及多对多关联关系的加载策略。Dept.hbm.xml文件中的如下代码配置了Dept类Emp类的一对多映射关系。

  1. <set name="emps" inverse="true" lazy="true">
  2. <key column="DEPTNO"/>
  3. <one-to-many class="cn.demo.po.Emp"/>
  4. </set>

说明:元素的lazy属性的取值决定了emps集合被初始化的时机,即到底是在加载Dept对象时就被初始化,还是在程序访问emps集合时被初始化。<set>元素的lazy取值如下:

  • true:默认值,延迟加载
  • false:立即加载
  • extra:增强延迟加载

(1)立即加载

如何设置: <set name="emps" inverse="true" lazy="false">...</set>
例:

  1. tx=session.beginTransaction();
  2. Dept dept=(Dept)session.get(Dept.class,10);
  3. tx.commit();

说明:执行Session的get()方法时,对于Dept对象采用类级别的立即加载策略;对于Dept对象的emps集合(即与Dept关联的所有Emp对象)采用一对多关联级别的立即加载策略。因此Hibernate会执行以下select语句:

  1. select * from dept where deptno=? //根据部门ID查询指定部门
  2. select * from emp where DEPTNO=? //根据部门ID获取Emp对象

通过以上select语句,Hibernate加载了一个Dept对象和多个Emp对象。在很多情况下,应用程序并不需要访问这些Emp对象,所以在一对多关联级别中不能随意使用立即加载策略。

(2)延迟加载
对于<set>元素,应该优先考虑使用默认的延迟加载策略。
如何设置:

  1. <set name="emps" inverse="true"></set>

  1. <set name="emps" inverse="true" lazy="true"></set>

再次运行以下代码,仅立即加载Dept对象,仅执行select语句:
select * from dept where deptno=?

  1. tx=session.beginTransaction();
  2. Dept dept=(Dept)session.get(Dept.class,10);
  3. tx.commit();

说明: Session的get()方法的Dept对象中,emps属性引用一个没有被初始化的集合代理类实例。换句话说:此时emps集合中没有存放任何Emp对象。只有当emps集合代理类实例被初始化时,才会到数据库中查询所有与Dept关联的Emp对象。

Dept对象的emps属性引用的集合代理类实例何时初始化:两种情况

  • 【1】会话关闭前,应用程序第一次访问它时,如调用它的iterator()、size()、isEmpty()或contains()方法。

    Set emps=dept.getEmps();

    1. //导致emps集合代理类实例被初始化
    2. Iterator<Emp> empIterator=emps.iterator();
  • 【2】会话关闭前,通过org.hibernate.Hibermate类的initialize()静态方法初始化。

    Set emps=dept.getEmps();

    1. //导致emps集合代理类实例被初始化
    2. Hibernate.initialize(emps);

(3)增强延迟加载

<set>元素中配置lazy属性为”extra”,表明采用增强延迟加载策略,如:

  1. <set name="emps" inverse="true" lazy="extra"></set>

说明: 增强延迟加载策略与一般的延迟加载策略(lazy=“true”)的主要区别在于,增强延迟加载策略能进一步延迟Dept对象的emps集合代理类实例的初始化时机。当程序第一次访问emps属性的iterator()方法时,会导致emps集合代理类实例的初始化,而当程序第一次访问emps属性的size()、contains()和isEmpty()方法时,Hibernate不会初始化emps集合代理实例,仅通过特定的select语句查询必要的信息。

例1:以下语句不会初始化emps集合代理类实例

  1. tx=session.beginTransaction();
  2. Dept dept=(Dept)session.get(Dept.class,10);
  3. //执行SQL语句:select count(empno) from emp where DEPTNO=?
  4. dept.getEmps().size();

例2:

  1. //以下语句会初始化emps集合代理类实例
  2. dept.getEmps().iterator();
  3. tx.commit();

3、配置多对一关联的查询加载策略

在映射文件中,元素用来设置多对一关联关系。在Emp.hbm.xml文件中,以下代码设置Emp类与Dept类的多对一关联关系:

  1. <many-to-one name="dept" column="DEPTNO" lazy="proxy"
  2. class="cn.demo.po.Dept"/>

元素的lazy属性设置不同取值时的加载策略如下:

  • proxy:默认值,延迟加载
  • no-proxy:无代理延迟加载
  • false:立即加载

如果没有显式设置lazy属性,那么在多对一关联级别采用默认的延迟加载策略。假如应用程序仅仅希望访问Emp对象,并不需要立即访问与Emp关联的Dept对象,则应该使用默认的延迟加载策略。

(1)延迟加载

在元素中配置lazy属性为“proxy”,延迟加载与Emp关联的Dept对象。
例:

  1. tx=session.beginTransaction();
  2. Emp emp=(Emp)session.get(Emp.class,7839);
  3. //emp.getDept() 返回Dept代理类实例的应用
  4. Dept dept=emp.getDept();
  5. dept.getDeptName();
  6. tx.commit();

说明:
当运行Session的get()方法时,仅立即执行查询Emp对象的select语句:

  1. select * from Emp where empno=?

Emp对象的dept属性引用Dept代理类实例,这个代理类实例的OID由EMP表的DEPTNO外键值决定。当执行dept.getDeptName()方法时,Hibernate初始化Dept代理类实例,执行以下select语句从数据库中加载Dept对象:

  1. select * from dept where deptno=?

(2)无代理延迟加载

在元素中配置lazy属性为“no-proxy”,如下

  1. <many-to-one name="dept" column="DEPTNO" lazy="no-proxy" class="cn.demo.po.Dept"/>

对于以下代码:

  1. tx=session.beginTransaction();
  2. Emp emp=(Emp)session.get(Emp.class,7839); //第1行
  3. Dept dept=emp.getDept(); //第2行
  4. dept.getDeptName(); //第3行
  5. tx.commit();

说明:

  • 如果对Emp对象的dept属性使用无代理延迟加载,即元素的lazy属性为“no-proxy”,那么程序第一行加载的Emp对象的dept属性为null。
    当程序第二行调用emp.getDept()方法时,将触发Hibernate执行查询DEPT表的selec语句,从而加载Dept对象。
  • 由此可见,当lazy属性为“proxy”时,可以延长延迟加载Dept对象的时间。而当lazy属性为“no-proxy”时,则可以避免使用由Hibernate提供的Dept代理类实例,使Hibernate对程序提供更加透明的持久化服务。
  • 注意:当lazy属性为“no-proxy”时,需要在编译期间进行字节码增强操作,否则运行情况和lazy属性为“proxy”时相同,因此很少用到。

(3)立即加载

以下代码把Emp.hbm.xml文件中<many-to-one>元素的lazy属性设置为false:

  1. <many-to-one name="dept" column="DEPTNO" lazy="false" class="cn.demo.po.Dept"/>

对于以下程序代码:

  1. tx=session.beginTransaction();
  2. Emp emp=(Emp)session.get(Emp.class,7839);
  3. tx.commit();

在运行session.get()方法时,Hibernate会执行以下select语句:

  1. select * from emp where empno=? //根据empno查询员工
  2. select * from dept where deptno=? //根据查询的员工对象中的deptno查部门

4、配置OpenSessionInView模式

在Java Web应用中,通常需要调用Hibernate API获取要显示的某个对象并传给相应的视图JSP,并在JSP中根据需要通过这个对象导航到与之关联的对象或集合数据。这些关联对象或集合数据如果是被延迟加载的,且在执行完查询后Session对象已经关闭,Hibernate就会抛出LazyInitializationException异常。针对这一问题,Hiber社区提出了Open Session In View模式作为解决方案。这个模式的主要思想是:在用户的每一次请求过程中,始终保持一个Session对象处于开启状态。

Open Session In View模式的具体实现有以下三个步骤:

(1)第一步: 把Session绑定到当前线程上,要保证在一次请求中只有一个Session对象,Dao层的HibernateUtil.currentSession()方法使用SessionFactory的getCurrentSession()方法获得Session,可以保证每一次请求的处理线程上只有一个Session对象存在。

(2)第二步: 用Filter过滤器在请求到达时打开Session,在页面生成完毕时关闭Session。代码如下:

OpenSessionInViewFilter.java

  1. package cn.demo.utils;
  2. import org.hibernate.HibernateException;
  3. import org.hibernate.Transaction;
  4. import javax.servlet.*;
  5. import java.io.IOException;
  6. public class OpenSessionInViewFilter implements Filter {
  7. @Override
  8. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  9. Transaction tx=null;
  10. try {
  11. //请求到达时,打开Session并启动事务
  12. tx=HibernateUtil.currentSession().beginTransaction();
  13. //执行请求处理链
  14. filterChain.doFilter(servletRequest,servletResponse);
  15. //返回响应时,提交事务
  16. tx.commit();
  17. }catch (HibernateException e){
  18. e.printStackTrace();
  19. if(tx!=null){
  20. tx.rollback(); //回滚事务
  21. }
  22. }
  23. }
  24. }

web.xml中配置过滤器

  1. <webapp>
  2. <!--配置OpenSessionInView-->
  3. <filter>
  4. <filter-name>openSessionInView</filter-name>
  5. <filter-class>cn.bdqn.utils.OpenSessionInViewFilter</filter-class>
  6. </filter>
  7. <filter-mapping>
  8. <filter-name>openSessionInView</filter-name>
  9. <url-pattern>/*</url-pattern>
  10. </filter-mapping>
  11. </webapp>

说明:每一次请求都在OpenSessionInViewFilter过滤器中打开Session,开启事务;页面生成完毕之后,在OpenSessionInViewFilter过滤器中结束事务并关闭Session。

(3)第三步: 调整业务层代码,删除和会话及事务管理相关的代码,仅保留业务逻辑代码。

DeptBizImpl.java代码如下:

  1. public class DeptBizImpl implements DeptBiz{
  2. private DeptDao deptDao=new DeptDao();
  3. @Override
  4. public Dept findDeptByDeptNo(Integer deptNo)throws Exception{
  5. return this.deptDao.findById(deptNo);
  6. }
  7. }

数据访问层代码风格不变。

小结:在OpenSessionInViewFilter过滤器中获取Session对象,保证一次请求过程中始终使用一个Session对象。视图JSP从一个对象导航到与之关联的对象或集合,即使这些对象或集合是被延迟加载的,因为当前Session对象没有关闭,所以能顺利的获取到关联当对象或集合的数据。直到视图JSP数据全部响应完成,OpenSessionInViewFilter过滤器才结束事务并关闭Session。

发表评论

表情:
评论列表 (有 0 条评论,331人围观)

还没有评论,来说两句吧...

相关阅读

    相关 Hibernate--关联映射

           hibernate是持久层ORM框架,关系映射解决了对象型实体与关系型数据库不相匹配的问题,它是Hibernate的核心内容,在实际操作中我们必须要根据数据表的相

    相关 Hibernate 03-配置关联关系映射

    一、了解关联关系 类与类之间最普遍的关系就是关联关系,并且关联是有方向的。以部门(Dept)和员工(Emp)类为例,一个部门下有多个员工,而一个员工只能属于一个部门