"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http:// hibernate. sourceforge.net/hibernate-mapping-3.0.dtd">
Hibernate的双向关联除了在主表(当前可以看作room表)的映射文件中设置一对多(one-to-many)外,还需要在从表(当前可以看作userinfo表)设置多对一(many-to-one)。首先需要在UserInfo.java实体类中增加一个Room实体类型的属性,其代码实现见例6.13。
在保证Room实体的映射不变,即Room.hbm.xml不变的情况下,需要修改UserInfo.hbm.xml来实现多对一。其映射配置见例6.14。
"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate. sourceforge.net/hibernate-mapping-3.0.dtd">
完成所有这些之后双向关联就配置完毕。
6.2.5 双向关联的实现和问题
接着该是双向关联的实现了,将例6.11的实现代码直接使用,为了呈现结果,可以通过UserInfo实体获取Room实体,再增加一个事务来打印结果,其代码实现见例6.15。
例6.15:双向关联的实现代码
public void run() {
//与例6.4相同
…
//创建一个新事务来获取Room实体
session = HibernateSessionFactory.currentSession();
tx = session.beginTransaction();
//根据room表的主键抓取Room持久化实体
Room room1 = (Room)session.get(Room.class, room.getId());
//打印各实体信息
System.out.println("Name:" + room1.getName());
System.out.println("Roomnumber:" + room1.getRoomnumber());
System.out.println("Id:" + room1.getId());
//抓取从表UserInfo实体的集合,并迭代打印结果
Iterator it = room1.getUsers().iterator();
while (it.hasNext()) {
UserInfo userInfoin = (UserInfo)it.next();
System.out.println("Id:" + userInfoin.getId());
System.out.println("Name:" + userInfoin.getName());
System.out.println("Roomid:" + userInfoin.getRoomid());
System.out.println("Sex:" + userInfoin.getSex());
//通过UserInfo实体的room属性获取Room实体的内容
System.out.println("RoomId:" + userInfoin.getRoom().getId());
System.out.println("Name:" + userInfoin.getRoom().getName());
System.out.println("Roomnumber:"+userInfoin.getRoom(). getRoomnumber());
}
tx.commit();
HibernateSessionFactory.closeSession();
}
下面执行例6.11的插入代码,来完成双向关联,其SQL语句如下:
Hibernate:
/* 插入room表 */
insert
into
room
(NAME, roomnumber, id)
values
(?, ?, ?)
Hibernate:
/*插入userinfo表,此时roomid为空*/
insert
into
userinfo
(NAME, SEX, roomid, id)
values
(?, ?, ?, ?)
Hibernate:
/*通过一对多的关联映射更新userinfo表中当前记录的roomid字段
实现room表与userinfo表的关联*/
update
userinfo
set
roomid=?
where
id=?
在执行例6.15的代码后打印结果如下:
Id:11117
Name:rw
Roomid:0
Sex:M
RoomId:25
Name:rwhome
Roomnumber:001
可以看到,通过Room实体获取UserInfo实体,再反向获取Room实体完成了。
只是这么实现的话,并没有达到最好的效果。因为SQL执行插入时总是要执行三句SQL,这样在效率上是有问题的。要达到效率上的提高就需要做另一个实现,那就是在配置文件中加入inverse属性。
6.2.6 inverse属性与双向关联
使用双向关联执行三句SQL的原因在于:插入room表后,需要插入根据一对多关联的userinfo表,但是插入userinfo表的前提是session.save(room);,也即通过Room实体来维护二者之间的关系。这也就意味着Room实体需要通过自身包含的UserInfo实体一一更新其外键,达到关联的目的。
而inverse属性就提供了另外一个更好的做法,它将关联关系反向交给UserInfo实体来完成,这也就意味着虽然通过session.save(room);来执行插入,但是却是由UserInfo实体来维护二者之间的关系。所做的更改有两个地方,首先是对Room. hbm.xml中一对多部分的修改,见例6.16。
例6.16:增加inverse属性的一对多
其次还需要在实现代码中,将UserInfo与Room实体的关系告诉UserInfo实体,也即让userinfo表的记录得到room表记录的主键。这段实现代码见例6.17。
例6.17:UserInfo实体参考Room实体
public void run() {
//创建Room实体
Room room = new Room();
//设置Room.name
room.setName("rwhome");
//设置Room.roomnumber
room.setRoomnumber("001");
//创建UserInfo实体
UserInfo userInfo = new UserInfo();
//设置UserInfo.name
userInfo.setName("rw");
//设置UserInfo.sex
userInfo.setSex("M");
//保证UserInfo实体得到与Room实体的关系,以帮助由UserInfo来维护外键关联
userInfo.setRoom(room);
//创建UserInfo集合userInfoSet
Set userInfoSet = new HashSet();
//添加UserInfo实体到集合userInfoSet
userInfoSet.add(userInfo);
//设置Room.users(这是一个集合类型)
room.setUsers(userInfoSet);
//创建Hibernate Session
Session session = HibernateSessionFactory.currentSession();
//启动事务
Transaction tx = session.beginTransaction();
//持久化Room实体
session.save(room);
//提交事务
tx.commit();
//关闭Hibernate Session
HibernateSessionFactory.closeSession();
}
执行插入表操作,其显示出来的SQL语句如下:
Hibernate:
/* 插入room表 */
insert
into
room
(NAME, roomnumber, id)
values
(?, ?, ?)
Hibernate:
/*插入userinfo表,此时roomid通过UserInfo参考Room实体已经获取并插入了*/
insert
into
userinfo
(NAME, SEX, roomid, id)
values
(?, ?, ?, ?)
这样的SQL语句在批量插入userinfo表时效率高了许多,是双向关联中效率最高的一种插表方式。值得注意的是,执行插表语句中的userInfo.setRoom(room);必须写在代码中,否则SQL语句同样是执行两句插入,但是在userinfo表中将会插入一个为null的roomid。
6.2.7 结语
单向关联的功能比双向关联要弱,而且单向关联在操作数据库表时总是会执行三句SQL。因此在一般设计和实现中,通常应该优先选择使用双向关联。而使用双向关联时,inverse属性也是不能忽视的一个重点。通过多端来控制外键值的插入是值得推荐的。