一对一映射有两种方式:主键关联和外键关联。
以User类和IdCard类为例,用户和身份证是一对一的双向关联关系。
持久化类User:
public class User { private Long id; private String name; private IdCard idCard; //省略set、get方法 }持久化类IdCard:
public class IdCard { private Long id; private String number; private User user; //省略set、get方法 }
1.主键关联即其中一个表的主键参照另外一张表的主键而建立起一对一关联关系,配置如下:
User.hbm.xml配置:
<hibernate-mapping> <class name="bean.User" table="users"> <id name="id" column="id" type="long"> <generator class="increment"></generator> </id> <property name="name" column="name" type="string"></property> <!--使用<one-to-one>表示一对一关联--> <one-to-one name="idCard" class="bean.IdCard" cascade="all"></one-to-one> </class> </hibernate-mapping>
IdCard.hbm.xml配置:
<hibernate-mapping>
<class name="bean.IdCard" table="idCards" >
<id name="id" column="id" type="long">
<!-- 表示该表的主键值参照“user属性对应的表的主键”的值,注意:但id并不是外键! -->
<generator class="foreign">
<param name="property">user</param>
</generator>
</id>
<property name="number" column="number" type="string"></property>
<!-- 一对一关联 -->
<one-to-one name="user" class="bean.User" ></one-to-one>
</class>
</hibernate-mapping>
保存对象:
User user=new User(); user.setName("zhangsan"); IdCard card=new IdCard(); card.setNumber("1002711"); user.setIdCard(card); card.setUser(user); session.save(user);输出的SQL语句为:
Hibernate: insert into users (name, id) values (?, ?) Hibernate: insert into idCards (number, id) values (?, ?)
查询对象:
u=(User)session.get(User.class, 1L); System.out.println( u.getIdCard().getNumber());输出的SQL语句为:
Hibernate: select user0_.id as id0_2_, user0_.name as name0_2_, idcard1_.id as id1_0_, idcard1_.number as number1_0_, user2_.id as id0_1_, user2_.name as name0_1_ from users user0_ left outer join idCards idcard1_ on user0_.id=idcard1_.id left outer join users user2_ on idcard1_.id=user2_.id where user0_.id=?
说明:
(1)由于采用了主键关联方式,那通过主键关联的两张表,其关联记录的主键值须保持同步。这也就意味着,我们只需为一张表(此处为users表)设定主键生成器,而另一张表(即idCards表)的主键与之共享相同的主键值。但是idCards表中的主键id并不是参照users表id的外键。
(2),虽然在User类和IdCard类之间有互相引用关系,但是数据库端users和idCards表没有任何参照关系,只是当我们保存User对象时,若User中的idCard属性值不为NULL,此时Hibernate就会以Use.hbm.xml中使用的主键生成器生成的主键值作为向users表和idCards表中插入记录的主键值,此时保持了关联记录的主键值的同步(由输出的SQL语句可知这是由Hibernate处理的,数据库端并不知情)
(3)两表结构:
(4)由查询语句可知主键关联使用的是左外连接,可以通过修改<one-to-one>中的fetch属性值为"select"修改成每次发送一条select语句的形式(必须将关联的两端的<one-to-one>中的fetch都设为"select"才有效)。但这会降低效率(因为需发送多条select语句)。
(5)主键关联中默认使用立即加载策略,在<one-to-one>中设置lazy="proxy"是无法使用延迟加载策略的(即设置lazy属性是无效的)。
(6)对于<one-to-one>中的constrained属性(关联约束)的说明:
——constrained属性值默认为false,当constrained为false时,生成的两个表之间无任何参照关系,并且使用的是立即加载策略。
——如果将constrained属性值改为true:
——(1)如果执行的是保存对象的语句,由于我使用的是Hibernate自动建表<propertyname="hibernate.hbm2ddl.auto">update</property>表不存在时建表,表存在时更新表的结构。如果表不存在:
——若User.hbm.xml中的constrained为true,建立两表并建立参照关系,users表中的id将是参照idCards表中id的外键(若删除上面的user.setIdCard(card)自 然就会抛出异常啦,因为users的id参照的是idCards中的id,并且两者主键值同步);生成的两表结构及参照关系为:
——若IdCard.hbm.xml中的constrained为true,建立两表并建立参照关系,idCards表中的id将是参照users表id的外键。生成的两表结构及参照关系为:
——若两者的constrained都为true,虽然会在数据库中建立两个相互参照的表users和idCards表,但是保存对象时就会抛异常导致插入记录失败。生成的两表结构及参照关系为:
(如果表存在或不使用Hibernate的建表功能就不会出现这些情况了)。
——(2)如果执行的是查询对象的语句,假定表已存在:
——若User.hbm.xml中的<one-to-one>constrained为true,则当 user=(User)session.get(User.class, 1L);延迟加载User对象中的idCard。
——若IdCard.hbm.xml中<one-to-one>的constrained为true,则当card=(IdCard)session.get(IdCard.class, 1L);延迟加载IdCard对象中的user。
——若User.hbm.xml和IdCard.hbm.xml中的<one-to-one>constrained为true,就是上述两种情况的综合,无论查询哪类对象都会延迟加载它引用的一方。
Hibernate处理一对一双方都延迟加载的特殊情况:
try{可知由于查询的都是id为1L的对象且是一对一双向关联,Hibernate就知道它们是相互引用的对象,只需两条select语句就可以将user对象和card对象
全部" 填充",所以在session关闭之后执行user.getIdCard()和card.getUser()时并不会抛出org.hibernate.LazyInitializationException异常。
——若User.hbm.xml和IdCard.hbm.xml中的<one-to-one>constrained为false,则就是我们以前的情况,使用立即加载策略(constrained默认就是false)
对于一对一主键关联最好是使两表之间具有参照关系,查询时使用默认的左外连接查询。
2.外键关联。本质上是一对多的蜕化形式。在<many-to-one>元素中设置属性unique=”true”就变成了一对一。
修改User.hbm.xml中的<one-to-one>为<many-to-one>:
<many-to-one name="idCard" class="bean.IdCard" cascade="all" update="true"></many-to-one>修改IdCard.hbm.xml中的<one-to-one>为<many-to-one> 且自己生成主键:
<id name="id" column="id" type="long"> <generator class="increment"></generator> </id> <many-to-one name="user" class="bean.User" cascade="all" unique="true"></many-to-one>其他地方不需要修改。
与主键关联不同的是,生成的表的结构不同,如下:
可以看出两表中各多了一个外键,两表之间是相互参照关系。
保存对象:
User user=new User(); user.setName("zhangsan"); IdCard card=new IdCard(); card.setNumber("1002711"); user.setIdCard(card); card.setUser(user); session.save(user);
Hibernate: insert into idCards (number, user_id, id) values (?, ?, ?) Hibernate: insert into users (name, card_id, id) values (?, ?, ?) Hibernate: update idCards set number=?, user_id=? where id=?说明:因为save的是user对象,所以在此之前需保存card对象,在向idCards表中插入记录时并不知道user_id的值(因为尚未保存对应的user对象,所以先给赋一个user_id默认值NULL),然后保存对象user,此时可以得到正确的user_id,所以最后再update更新idCards表中与之对应的记录的user_id值。若将card.setUser(user)这句注释掉,则就不会输出上述的update语句。
查询对象:
user=(User)session.get(User.class, 1L);
Hibernate: select user0_.id as id0_0_, user0_.name as name0_0_, user0_.card_id as card3_0_0_ from users user0_ where user0_.id=?可知对于外键关联的一对一关联关系,默认的加载策略是延迟加载,将其改为立即加载的方式:
——将lazy属性值改为false,如不希望user=(User)session.get(User.class, 1L);延迟加载,则需将User.hbm.xml中<many-to-one>的属性值改为false。
——将fetch属性设为"join",这比改lazy为false效率更高。
转载请注明出处:http://blog.csdn.net/jialinqiang/article/details/8709275