Hibernate的一对一关联关系

悠悠 2022-07-14 09:28 339阅读 0赞

  Hibernate的一对一关联关系,分为基于外键的一对一关联关系和基于主键的一对一关联关系。在这篇文章中,我们以部门和部门经理的例子来说明,一个部门对应唯一一个部门经理,一个部门经理也对应唯一一个部门。
  在基于外键的一对一关联关系中,一端通过一个主键以外的字段关联另一端的主键,如下图所示:
  E3CF6A0B-55EB-43AE-BDBC-D2DB80C116EF.jpg-8.3kB
   
  在基于主键的一对一关联关系中,一端直接通过主键关联另一端的主键,并通过另一端的主键生成自己的主键,如下图所示:
  image\_1b36lr2j11khlidodn5i5e1net12.png-22.9kB
   
  下面进行详细说明。

基于外键的一对一关联关系

  对于基于外键的1-1关联关系,外键可以存放在任意一端,例如在本例中,外键既可以存放在department一端,也可以存放在manager一端,我们假设存放在department一端。在需要存放外键的一端,增加many-to-one节点,并且为many-to-one节点添加unique=”true”属性,来表示为1-1关联,添加unique=”true”属性了以后,不同的department就不能关联同一个manager了。在不存放外键的一端,需要使用one-to-one节点,并且在该节点中添加property-ref属性来指定存放外键一端的除主键以外的字段来作为关联字段。代码如下,首先创建两个类:

  1. public class Department {
  2. private Integer deptId;
  3. private String deptName;
  4. private Manager mgr;
  5. //getters and setters
  6. }
  7. public class Manager {
  8. private Integer mgrId;
  9. private String mgrName;
  10. private Department dept;
  11. //getters and setters
  12. }

映射文件:
 
Department.hbm.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  3. <hibernate-mapping>
  4. <class name="com.atguigu.hibernate.one2one.foreign.Department" table="DEPARTMENTS">
  5. <id name="deptId" type="java.lang.Integer">
  6. <column name="DEPT_ID" />
  7. <generator class="native" />
  8. </id>
  9. <property name="deptName" type="java.lang.String">
  10. <column name="DEPT_NAME" />
  11. </property>
  12. <!-- 使用 many-to-one 的方式来映射 1-1 关联关系 -->
  13. <!-- 添加unique="true"属性,来表示为1-1关联 -->
  14. <many-to-one name="mgr" class="com.atguigu.hibernate.one2one.foreign.Manager" column="MGR_ID" unique="true"></many-to-one>
  15. </class>
  16. </hibernate-mapping>

Manager.hbm.xml  

  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  3. <hibernate-mapping>
  4. <class name="com.atguigu.hibernate.one2one.foreign.Manager" table="MANAGERS">
  5. <id name="mgrId" type="java.lang.Integer">
  6. <column name="MGR_ID" />
  7. <generator class="native" />
  8. </id>
  9. <property name="mgrName" type="java.lang.String">
  10. <column name="MGR_NAME" />
  11. </property>
  12. <!-- 映射 1-1 的关联关系: 在对应的数据表中已经有外键了, 当前持久化类使用 one-to-one 进行映射 -->
  13. <!-- 没有外键的一端需要使用one-to-one元素,该元素使用 property-ref 属性指定使用被关联实体主键以外的字段作为关联字段 -->
  14. <one-to-one name="dept" class="com.atguigu.hibernate.one2one.foreign.Department" property-ref="mgr"></one-to-one>
  15. </class>
  16. </hibernate-mapping>

运行一个空的test程序,可以生成数据库表managers和departments:
 
image\_1b36mlbre1tev98d1f9rusbiu91f.png-19kB
 
image\_1b36mmdvq19lu1o681ehs1sgc1gmh1s.png-19.8kB
image\_1b36mmu0n1qp61tpek735sbdfr29.png-18.9kB

下面测试基于外键的1-1关联关系的save和get操作:

基于外键的1-1关联关系的save操作:

  1. @Test
  2. public void testSave(){
  3. Department department = new Department();
  4. department.setDeptName("DEPT-BB");
  5. Manager manager = new Manager();
  6. manager.setMgrName("MGR-BB");
  7. //设定关联关系
  8. department.setMgr(manager);
  9. manager.setDept(department);
  10. //保存操作
  11. //建议先保存没有外键列的那个对象. 这样会减少 UPDATE 语句
  12. session.save(manager);
  13. session.save(department);
  14. }

  这段代码可以正常插入记录。和n-1关联关系中一样,如果先保存了存放外键一端的对象,后保存被外键关联的一端的对象,即如果先执行session.save(department);,后执行session.save(manager);,虽然同样可以正确保存,但是会多出一条update语句用于维护关联关系,所以通常建议先插入没有外键一端的对象,后插入有外键一端的对象。
  
基于外键的1-1关联关系的get操作:

  1. @Test
  2. public void testGet(){
  3. //1. 默认情况下对关联属性使用懒加载
  4. Department dept = (Department) session.get(Department.class, 1);
  5. System.out.println(dept.getDeptName());
  6. //2. 所以会出现懒加载异常的问题.
  7. // session.close();
  8. // Manager mgr = dept.getMgr();
  9. // System.out.println(mgr.getClass());
  10. // System.out.println(mgr.getMgrName());
  11. //3. 查询 Manager 对象的连接条件应该是 dept.manager_id = mgr.manager_id
  12. //而不应该是 dept.dept_id = mgr.manager_id
  13. Manager mgr = dept.getMgr();
  14. System.out.println(mgr.getMgrName());
  15. }

  同n-1关联关系一样,当查询存放外键的一端的对象department的时候,使用懒加载机制,即不会立即加载它关联的另一端的对象manager,而只有等到要使用manager的时候,才会发送select语句加载。那么同样也有可能发生懒加载异常。运行结果如下图所示:
  image\_1b36o9f1j1a1mss9i951atf1cqh2m.png-65.3kB
  值得注意的是,当要使用到manager对象时,是通过左外连接查询到manager对象的,连接条件是dept.manager_id = mgr.manager_id,这是正确的,因为我们在Manager.hbm.xml中的one-to-one节点中配置了property-ref=”mgr”,指定关联字段为department的mgr字段。如果没有设置property-ref属性,那么默认关联的字段为department的id字段,例如,我们去掉property-ref=”mgr”的设置,运行testGet()方法,则会打印如下的sql语句(连接条件是dept.dept_id = mgr.manager_id),这显然是不符合需求的。
  image\_1b36vjp9k2tp1jum6srtu7bs833.png-35.3kB
  
  还有一个注意点,就是当首先查询不存放外键的一端的对象时,即manager,由于其中没有设置外键关联到department,所以会使用左外连接查询,一并查询出另一端的对象,即department,而且已经完成了初始化。如下所示:

  1. @Test
  2. public void testGet2(){
  3. //在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
  4. //并已经进行初始化.
  5. Manager mgr = (Manager) session.get(Manager.class, 1);
  6. //在执行下面的代码之前已经完成了对department对象的初始化
  7. System.out.println(mgr.getMgrName());
  8. System.out.println(mgr.getDept().getDeptName());
  9. }

运行结果:
image\_1b37027r68l01dvj1j2u1dnu14ua3t.png-33.9kB
  
  关于基于外键的一对一关系,还有一点值得注意,不能在两端都使用外键映射为1-1,例如下面这种情况,表department表和manager都分别设置了外键manager_id和department_id,那么当一条manager记录单向关联了一条department记录,而这条department记录却关联向另一条manager记录,就会出现问题,如下图所示:
  image\_1b372rtct32gnaf1uu216bkcs94a.png-92kB        

基于主键的一对一关联关系

  基于主键的1-1映射策略,是指一端的主键生成器使用foreign策略,表明根据“对方”的主键来生成自己的主键,自己并不能独立生成主键。子节点指定使用当前持久化类的哪一个属性来作为“对方”。例如:

  1. <generator class="foreign">
  2. <!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
  3. <param name="property">mgr</param>
  4. </generator>

  采用foreign主键生成器策略的一端使用one-to-one元素映射关联属性,需要在one-to-one节点中设置constrained=”true”,以指定为当前持久化类对应的数据库表的主键添加一个外键约束,引用被关联的对象(即“对方”)所对应的数据库表的主键。另一端同样使用one-to-one节点映射关联关系。
  下面我们仍以department和manager的例子进行测试,首先新建两个类:

  1. public class Department {
  2. private Integer deptId;
  3. private String deptName;
  4. private Manager mgr;
  5. //getters and setters
  6. }
  7. public class Manager {
  8. private Integer mgrId;
  9. private String mgrName;
  10. private Department dept;
  11. //getters and setters
  12. }

映射文件:
 
Department.hbm.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  3. <hibernate-mapping package="com.atguigu.hibernate.one2one.primary">
  4. <class name="Department" table="DEPARTMENTS">
  5. <id name="deptId" type="java.lang.Integer">
  6. <column name="DEPT_ID" />
  7. <!-- 使用外键的方式来生成当前的主键 -->
  8. <generator class="foreign">
  9. <!-- property 属性指定使用当前持久化类的哪一个属性的主键作为外键 -->
  10. <param name="property">mgr</param>
  11. </generator>
  12. </id>
  13. <property name="deptName" type="java.lang.String">
  14. <column name="DEPT_NAME" />
  15. </property>
  16. <!-- 采用 foreign 主键生成器策略的一端增加 one-to-one 元素映射关联属性, 其 one-to-one 节点还应增加 constrained=true 属性, 以使当前的主键上添加外键约束 -->
  17. <one-to-one name="mgr" class="Manager" constrained="true"></one-to-one>
  18. </class>
  19. </hibernate-mapping>

Manager.hbm.xml

  1. <?xml version="1.0"?>
  2. <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
  3. <hibernate-mapping>
  4. <class name="com.atguigu.hibernate.one2one.primary.Manager" table="MANAGERS">
  5. <id name="mgrId" type="java.lang.Integer">
  6. <column name="MGR_ID" />
  7. <generator class="native" />
  8. </id>
  9. <property name="mgrName" type="java.lang.String">
  10. <column name="MGR_NAME" />
  11. </property>
  12. <one-to-one name="dept" class="com.atguigu.hibernate.one2one.primary.Department"></one-to-one>
  13. </class>
  14. </hibernate-mapping>

生成的数据库表如下:
 
managers
image\_1b373jjnq7vj1dck1j6f4qkvjg4n.png-16.2kB

departments
image\_1b373km8b96o52engkc4s197a54.png-16.9kB
image\_1b373l1mp1cm2161gs14amffm5h.png-17.9kB
可以看到,表departments是根据主键DEPT_ID来关联表managers的。
 
下面测试save和get方法:

基于主键的1-1关联关系的save操作:

  1. @Test
  2. public void testSave(){
  3. Department department = new Department();
  4. department.setDeptName("DEPT-AA");
  5. Manager manager = new Manager();
  6. manager.setMgrName("MGR-AA");
  7. //设定关联关系
  8. manager.setDept(department);
  9. department.setMgr(manager);
  10. //保存操作
  11. //先插入哪一个都不会有多余的 UPDATE
  12. session.save(department);
  13. session.save(manager);
  14. }

  和之前不同的是,不论是先执行session.save(department);,还是先执行session.save(manager);,效果都是一样的,都只有两条insert语句,不会有update语句,而且都会先执行insert into managers,后执行insert into departments,如下图:
  image\_1b374o1mk1nr6pit19rc154h1ais5u.png-22.1kB
  
  这是因为,现在department是根据主键关联manager,主键是不能像外键那样先被置为null然后进行update修改的,所以不论哪一个语句放在前面,都会先等到manager记录插入后,再插入department记录。
  
基于主键的1-1关联关系的get操作:

  1. @Test
  2. public void testGet(){
  3. //1. 默认情况下对关联属性使用懒加载
  4. Department dept = (Department) session.get(Department.class, 1);
  5. System.out.println(dept.getDeptName());
  6. //2. 所以会出现懒加载异常的问题.
  7. Manager mgr = dept.getMgr();
  8. System.out.println(mgr.getMgrName());
  9. }
  10. @Test
  11. public void testGet2(){
  12. //在查询没有外键的实体对象时, 使用的左外连接查询, 一并查询出其关联的对象
  13. //并已经进行初始化.
  14. Manager mgr = (Manager) session.get(Manager.class, 1);
  15. /*System.out.println(mgr.getMgrName()); System.out.println(mgr.getDept().getDeptName()); */
  16. }

  基于主键的1-1和基于外键的1-1十分相似,在查询department时都使用懒加载机制,可能会抛出懒加载异常,在查询manager时都会使用左外连接,但不同的是,我们在Manager.hbm.xml文件的one-to-one节点中没有设置property-ref属性,即默认department中关联manager的字段是department的id,这正是我们在基于主键的1-1关系中希望的,所以可以看到,左外连接的连接条件是dept.dept_id = mgr.manager_id:
  image\_1b376sjn8145n11rm1844dfshjg6r.png-39.6kB

发表评论

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

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

相关阅读

    相关 Hibernate 单向一对一关联

    纸上得来终觉浅 开始学习Hibernate的关联关系。一共有七种关联关系,可以分为单向和双向,单向只是A能够加载B,但B不能够加载A,双向可互相加载,详细如下: 1)单向

    相关 Hibernate一对一关联关系

      Hibernate的一对一关联关系,分为基于外键的一对一关联关系和基于主键的一对一关联关系。在这篇文章中,我们以部门和部门经理的例子来说明,一个部门对应唯一一个部门经理,一

    相关 mybatis一对一关联关系

      这篇文章介绍mybatis如何处理一对一的关联关系,假设现在有班级类(表)和教师类(表),一个教师对应一个班级,一个班级也只对应一个教师。    首先创建表并插入数