• hibernate 一对一(One-to-One)


    一对一(one-to-one)实例(Person-IdCard)

    一对一的关系在数据库中表示为主外关系.例如.人和身份证的关系.每个人都对应一个身份证号.我们应该两个表.一个是关于人信息的表(Person).别外一个是身份证相关信息的表(id_card).id_card表的主键对应该Person表的主键id,也是Person表的外键.有人才能有身份证.所以此例中Person是主表,id_card表为从表。

    hibernate的一对一关系有两种形式,一种是共享主键方式,另一种是唯一外键方式.

    一、共享主键方式实现一对一

    1. 实体类设计如下:

    Person类:

    Java代码  收藏代码
    1. package com.reiyen.hibernate.domain;  
    2.   
    3. public class Person {  
    4.   
    5.     private int id;  
    6.     private String name;  
    7.     private IdCard idCard;  
    8.   
    9.        //setter和getter方法  
    10. }  

     IdCard类:

    Java代码  收藏代码
    1. package com.reiyen.hibernate.domain;  
    2.   
    3. public class IdCard {  
    4.   
    5.     private int id;  
    6.     private Date authorizeDate;  
    7.     private Person person;  
    8.          
    9.        //setter和getter方法  
    10. }  

     2.映射文件:

    Person.hbm.xml文件如下:

    Xml代码  收藏代码
    1. <?xml version="1.0"?>  
    2. <!DOCTYPE hibernate-mapping PUBLIC   
    3.     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
    4.     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
    5. <hibernate-mapping package="com.reiyen.hibernate.domain">  
    6.     <class name="Person" >  
    7.         <id name="id" >  
    8.             <generator class="native" />  
    9.         </id>  
    10.         <property name="name" />  
    11.         <one-to-one name="idCard" />  
    12.     </class>  
    13. </hibernate-mapping>  

     IdCard.hbm.xml文件如下:

    Xml代码  收藏代码
    1. <?xml version="1.0"?>  
    2. <!DOCTYPE hibernate-mapping PUBLIC   
    3.     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
    4.     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
    5. <hibernate-mapping package="com.reiyen.hibernate.domain">  
    6.     <class name="IdCard" table="id_card">  
    7.         <id name="id">  
    8.             <!-- id_card的主键来源person,也就是共享idCard的主键 -->  
    9.             <generator class="foreign">  
    10.                 <param name="property">person</param>  
    11.             </generator>  
    12.         </id>  
    13.         <property name="authorizeDate" column="authorize_date" />  
    14.         <!-- one-to-one标签的含义,指示hibernate怎么加载它的关联对象,默认根据主键加载,   
    15.             constrained="true", 表明当前主键上存在一个约束,id_card的主键作为外键参照了person -->  
    16.         <one-to-one name="person" constrained="true"></one-to-one>  
    17.     </class>  
    18. </hibernate-mapping>  

    3. 在hibernate.cfg.xml文件中注册映射文件:

    Xml代码  收藏代码
    1. <mapping resource="com/reiyen/hibernate/domain/Person.hbm.xml" />  
    2. <mapping resource="com/reiyen/hibernate/domain/IdCard.hbm.xml" />  

     4.测试类如下:

    Java代码  收藏代码
    1. public class One2One {  
    2.     public static void main(String[] args) {  
    3.         Person person = add();  
    4.         System.out.println("peron name:" + person.getName());  
    5.     }  
    6.       
    7.     static Person add(){  
    8.         Session session = null;  
    9.         Transaction tran = null;  
    10.         try {  
    11.             session = HibernateUtil.getSession();  
    12.             IdCard idCard = new IdCard();  
    13.             idCard.setAuthorizeDate(new Date());  
    14.   
    15.             Person p = new Person();  
    16.             p.setName("person1");  
    17.              p.setIdCard(idCard);   // 1  
    18.             idCard.setPerson(p);   // 2  
    19.             tran = session.beginTransaction();  
    20.             session.save(p);  
    21.             session.save(idCard);    
    22.             tran.commit();  
    23.             return p;  
    24.         } finally {  
    25.             if (session != null)  
    26.                 session.close();  
    27.         }  

     控制台打印信息如下所示:

    Hibernate: insert into Person (name) values (?)
    Hibernate: insert into id_card (authorize_date, id) values (?, ?)
    person name : person1

    数据库表id_card的创建语句如下所示:(重点注意红色字体部分)

    DROP TABLE IF EXISTS `test`.`id_card`;
    CREATE TABLE  `test`.`id_card` (
      `id` int(11) NOT NULL,
      `authorize_date` datetime DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `FK627C1FB4284AAF67` (`id`),
      CONSTRAINT `FK627C1FB4284AAF67` FOREIGN KEY (`id`) REFERENCES `person` (`id`)

    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

    数据库中记录如下所示:
    mysql> select * from person;
    +----+---------+
    | id | name    |
    +----+---------+
    |  1 | person1 |
    +----+---------+
    1 row in set (0.00 sec)

    mysql> select * from id_card;
    +----+---------------------+
    | id | authorize_date      |
    +----+---------------------+
    |  1 | 2010-03-23 01:07:25 |
    +----+---------------------+
    1 row in set (0.00 sec)

    在测试时一定要注意写上这句代码:

    Java代码  收藏代码
    1. idCard.setPerson(p);   

    这是让从对象关联上它所从属的主对象。如果没有这句话,则会抛出如下异常:

    org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property: person

    5. 查询测试,测试类如下:

    Java代码  收藏代码
    1. public class One2One {  
    2.     public static void main(String[] args) {  
    3.         query(1);  
    4.     }  
    5.       
    6.     static void query(Integer id){  
    7.         Session session = null;  
    8.         try {  
    9.             session = HibernateUtil.getSession();  
    10.             Person person = (Person)session.get(Person.class, id);//4  
    11.                        System.out.println(person.getIdCard().getAuthorizeDate());//3  
    12.                         //IdCard idCard = (IdCard)session.get(IdCard.class, id); //1  
    13.             //System.out.println(idCard.getPerson().getName());      //2  
    14.         } finally {  
    15.             if (session != null)  
    16.                 session.close();  
    17.         }  
    18.     }  
    19. }  

     执行此测试类时,控制台打印信息如下所示:

    Hibernate: select person0_.id as id3_1_, person0_.name as name3_1_, idcard1_.id as id4_0_, idcard1_.authorize_date as authorize2_4_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
    2010-03-23 21:46:07.0

    从打印的SQL语句可以看出,一对一关系查询主对象时,然后得到主对象中的从属对象,通过left join一次就把查询结果查询出来了,因为从对象从属于主对象。而一对多,多对一关系时,从打印的SQL语句可知,它经过了两次查询才将查询结果查询出来。这是它们的区别。如果一对一关系中先查询从属对象,然后得到从属中的主对象时(即把上面测试类中的注释1, 2都去掉再运行),控制台打印信息如下:

    Hibernate: select idcard0_.id as id4_0_, idcard0_.authorize_date as authorize2_4_0_ from id_card idcard0_ where idcard0_.id=?
    Hibernate: select person0_.id as id3_1_, person0_.name as name3_1_, idcard1_.id as id4_0_, idcard1_.authorize_date as authorize2_4_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
    person1

    从打印的SQL可以看出,这种先查从对象,然后得到从属对象中的主对象,要经过两次查询才能得到查询结果。

    如果只把测试程序中注释1去掉运行,则只会执行上面两次查询中的第一次查询。

    6.one-to-one(元素)懒加载分析:

    必须同时满足下面的三个条件时才能实现懒散加载:1).lazy!=false (lazy缺省方式就!=false)2).constrained=true 3).fetch=select(fetch缺省方式即为select)
    (因为主表不能有constrained=true,所以主表没有懒加载功能)。能够懒加载的对象都是被改写过的代理对象,当相关联的session没有关闭时,访问这些懒加载对象(代理对象)的属性(getId和getClass除外)时,hibernate会初始化这些代理,或用Hibernate.initialize(proxy)来初始化代理对象;当相关联的session关闭后,再访问懒加载的对象将会出现异常。

    测试:(1).注释掉查询测试程序中标记为3的语句,运行程序,控制台打印信息如下:

    Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?

    说明查询主对象Person时没有懒加载特性,因此它通过left outer join id_card表,同时把它的从对象IdCard也查询出来了。那为什么一对一中查询主对象时,不能实现懒加载呢??大家可以看看person表的结构,你从表结构中根本不能决断出Person对象有没有相应的IdCard对象,所以它无法给setIdCard赋值(hibernate不能想当然的认为你有值,给你new一个代理对象给它),所以它一定要去查询相关联的对象表,看是否有与此Person对应的IdCard记录。而如果查询的是从对象IdCard时,因为idcard中的id是一个person表的一个外键,所以它必定有一个相对应的Person对象(因为有constrained=true),所以它可以先返回给你一个代理对象,当你真正需要Person对象的数据时,它再去查询数据库。

    (2).注释掉查询测试程序中标记为3,4的语句,同进将标记为1的语句前的注释去掉再运行程序,控制台打印信息如下:

    Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?

    从打印信息可以看出,查询从对象IdCard时实现了懒加载功能,因为它只查询了IdCard对象,而关联的Person对象它没有进行查询。

    (3).如果在(2)基础上将标记为2的语句前的注释也去掉再运行程序,控制台打印信息如下:

    Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?
    Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
    person1

    它就进行了两次查询,将IdCard关联的Person对象也进行了查询。因为访问这些懒加载对象(代理对象)的属性(getId和getClass除外)时,hibernate会初始化这些代理.

    (4).如果修改IdCard.hbm.xml映射文件,增加fetch="join",如下所示:

    Xml代码  收藏代码
    1. <one-to-one name="person" constrained="true" fetch="join"/>  

    再按(2)进行测试,此时控制台打印信息如下:

    Hibernate: select idcard0_.id as id5_1_, idcard0_.authorize_date as authorize2_5_1_, person1_.id as id4_0_, person1_.name as name4_0_ from id_card idcard0_ inner join Person person1_ on idcard0_.id=person1_.id where idcard0_.id=?
    此时查询从对象IdCard时也不再懒加载了,通过inner join一次性将主从对象都查询出来。

    (5).如果修改IdCard.hbm.xml映射文件,增加lazy="false",如下所示:

    Xml代码  收藏代码
    1. <one-to-one name="person" constrained="true" lazy="false" />  

    再按(2)进行测试,此时控制台打印信息如下:

    Hibernate: select idcard0_.id as id5_0_, idcard0_.authorize_date as authorize2_5_0_ from id_card idcard0_ where idcard0_.id=?
    Hibernate: select person0_.id as id4_1_, person0_.name as name4_1_, idcard1_.id as id5_0_, idcard1_.authorize_date as authorize2_5_0_ from Person person0_ left outer join id_card idcard1_ on person0_.id=idcard1_.id where person0_.id=?
    虽然也不实现懒加载功能,一次性将主从对象都查询出来,但此时是经过两次查询才得到结果。

    如果修改IdCard.hbm.xml映射文件,增加lazy="proxy",如下所示,与缺省时一样的效果,因为缺省时,lazy是=proxy

    Xml代码  收藏代码
    1. <one-to-one name="person" constrained="true" lazy="proxy" />  

    如果修改IdCard.hbm.xml映射文件,如下所示,则lazy(懒加载失效),此时效果如测试(4)。

    Xml代码  收藏代码
    1. <one-to-one name="person" constrained="true" lazy="proxy" fetch="join"/>  

    二、唯一外键方式实现一对一

    基于外键的one-to-one可以描述为多对一。

     hibernate一对一唯一外键关联映射(双向关联Person<---->IdCard)
    一对一唯一外键双向关联,需要在另一端(person),添加<one-to-one>标签,指示hibernate如何加载
    其关联对象,默认根据主键加载idcard,外键关联映射中,因为两个实体采用的是idcard的外键维护的关系, 所以不能指定主键加载idcard,而要根据idcard的外键加载,所以采用如下映射方式:
    <one-to-one name="idcard" property-ref="person"/>

    IdCard.hbm.xml的映射文件如下:

    Xml代码  收藏代码
    1. <?xml version="1.0"?>  
    2. <!DOCTYPE hibernate-mapping PUBLIC   
    3.     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
    4.     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
    5. <hibernate-mapping package="com.itcast.hibernate.domain">  
    6.     <class name="IdCard" table="id_card">  
    7.         <id name="id">  
    8.             <generator class="native" />  
    9.         </id>  
    10.         <property name="authorizeDate" column="authorize_date" />  
    11. <!-- 指定多的一端的unique=true,这样就限制了多的一端的多重性为一   
    12.             通过这种手段映射一对一唯一外键关联 -->  
    13.         <many-to-one name="person" column="person_id" unique="true" />  
    14.     </class>  
    15. </hibernate-mapping>  

     Person.hbm.xml的映射文件如下:

    Xml代码  收藏代码
    1. <?xml version="1.0"?>  
    2. <!DOCTYPE hibernate-mapping PUBLIC   
    3.     "-//Hibernate/Hibernate Mapping DTD 3.0//EN"  
    4.     "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">  
    5. <hibernate-mapping package="com.itcast.hibernate.domain">  
    6.     <class name="Person" >  
    7.         <id name="id" >  
    8.             <generator class="native" />  
    9.         </id>  
    10.         <property name="name" />  
    11.         <!-- 没有下面的one-to-one标签也行,但那样就变成了单向关联(IdCard ----》 Person) ,也就是当知道IdCard后,能找到它属于的对应的人,但知道某人后,却无法找到相对应的IdCard-->  
    12.         <one-to-one name="idCard" property-ref="person"/>  
    13.     </class>  
    14. </hibernate-mapping>  

     实体类不用修改,还是用上面的测试类进行测试即可。

    保存测试类运行后,相对共享主键方式的one-to-one,id_card表的结构发生了变化,表结构如下所示:

    DROP TABLE IF EXISTS `test`.`id_card`;
    CREATE TABLE  `test`.`id_card` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `authorize_date` datetime DEFAULT NULL,
      `person_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`),
      UNIQUE KEY `person_id` (`person_id`),
      KEY `FK627C1FB45B253C91` (`person_id`),
      CONSTRAINT `FK627C1FB45B253C91` FOREIGN KEY (`person_id`) REFERENCES `person` (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

    数据库表中记录如下:

    mysql> select * from person;
    +----+---------+
    | id | name    |
    +----+---------+
    |  1 | person1 |
    +----+---------+
    1 row in set (0.00 sec)

    mysql> select * from id_card;
    +----+---------------------+-----------+
    | id | authorize_date      | person_id |
    +----+---------------------+-----------+
    |  1 | 2010-03-23 22:40:38 |         1 |
    +----+---------------------+-----------+
    1 row in set (0.00 sec)

    如果Person.hbm.xml映射文件中没有<one-to-one/>这一项的话,运行测试:

    Java代码  收藏代码
    1. Person person = (Person)session.get(Person.class, id);  
    2. System.out.println(person.getIdCard().getAuthorizeDate());  

     会抛出如下异常:

     java.lang.NullPointerException

    因为这种关系成了IdCard--->Person的单向关联了。知道了Person,找不到对应的IdCard.

    当运行如下测试时:

    Java代码  收藏代码
    1. Person person = (Person)session.get(Person.class, id);  
    2. System.out.println(person.getIdCard());  

     控制台会打印出Person相对应的IdCard为null.

    但如果得到了IdCard,却能找到相应的Person.测试如下:

    Java代码  收藏代码
    1. IdCard idCard = (IdCard)session.get(IdCard.class, id);  
    2. System.out.println(idCard.getPerson().getName());  

     能得到正常的结果,person name为person1.

    总结: 在缺省情况下,hibernate只有在一对一关联中,查询主对象时,是进行关联查询一次得到查询结果,其它(多对多、多对一、一对多、一对一查询从对象)的查询都是分两次查询得到查询结果。
  • 相关阅读:
    Nginx + FastCGI 程序(C/C++)搭建高性能web service的demo
    微服务架构
    异常点/离群点检测算法——LOF
    多边形区域填充算法--递归种子填充算法
    Java 关于容器集合等数据结构详情图解,一目了然
    平衡小车项目解读日志
    &lt;LeetCode OJ&gt; 101. Symmetric Tree
    【JVM】模板解释器--字节码的resolve过程
    hexo博客的相关配置
    【LeetCode】Find Minimum in Rotated Sorted Array 解题报告
  • 原文地址:https://www.cnblogs.com/baobeiqi-e/p/9884825.html
Copyright © 2020-2023  润新知