`

Hibernate单、双向关联与inverse属性

阅读更多

1 Hibernate的单、双向关联

设计师L并不理解在Hibernate中单向关联与双向关联有什么区别。于是他也就无法告诉开发人员,在配置 实体间关系时究竟如何配置。从Hibernate的文档中来看,官方并没有特别告诉使用者哪种关联方式可靠。而且从L的实验结果来看,通常的一些行为使用 单向关联与双向关联的结果一样。那么究竟该如何看待这两种关联方式呢?

2 Hibernate的单向关联常规实现

依旧从实体房间(Room)与实体人(UserInfo)的一对多关联来看(这里将使用基于外键的一对多,对于连接表将放在另一个论题中),其两个实体如图6.2所示。

图6.2 两个实体的类图

实体房间(Room)与实体人(UserInfo)的代码实现见例6.8。

例6.8:实体房间(Room.java)与实体人(UserInfo.java)

Room.java

public class Room {

//房间实体的主键对应

private long id;

//房间号

private String roomnumber;

//房间名称

private String name;

//房间中的人(这是一个Set类型的集合)

private Set users;

//get/set方法

}

UserInfo.java

public class UserInfo {

//实体人的主键

private long id;

//人的名字

private String name;

//人的性别

private String sex;

//实体房间的主键

private long roomid;

//get/set方法

}

针对实体房间(Room)与实体人(UserInfo)的代码以及数据库表的单向一对多,其Hibernate映射文件见例6.9。

例6.9:实体房间(Room.java)与实体人(UserInfo.java)的映射文件

Room.hbm.xml

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http:// hibernate. sourceforge.net/hibernate-mapping-3.0.dtd">

<!-- Room实体的package -->

<hibernate-mapping package="testhibernate">

<!-- Room实体的class和表 -->

<class name="Room" table="room">

<!-- 映射id主键 -->

<id name="id" column="id" type="java.lang.Long">

<!-- 映射为自增长类型 -->

<generator class="increment" />

</id>

<!-- 映射name属性 -->

<property name="name" column="NAME" type="java.lang.String" />

<!-- 映射roomnumber属性 -->

<property name="roomnumber" column="roomnumber" type="java.lang.String" />

<!-- 映射一对多 -->

<!-- 通过Room实体的users集合属性映射,级联动作为全部 -->

<set name="users" cascade="all">

<!-- 映射User表的外键roomid -->

<key column="roomid"></key>

<!-- 一对多映射class UserInfo -->

<one-to-many class="UserInfo"></one-to-many>

</set>

</class>

</hibernate-mapping>

UserInfo.hbm.xml

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/HibernateMappingDTD3.0//EN""http://hibernate.sourceforge. net/hibernate-mapping-3.0.dtd">

<!-- UserInfo实体的package -->

<hibernate-mapping package="testhibernate">

<!-- UserInfo实体的class和表 -->

<class name="UserInfo" table="userinfo">

<!-- 映射id主键 -->

<id name="id" column="id" type="java.lang.Long">

<!-- 映射为自增长类型 -->

<generator class="increment" />

</id>

<!-- 映射name属性 -->

<property name="name" column="NAME" type="java.lang.String" not-null ="true" />

<!-- 映射roomnumber属性 -->

<property name="sex" column="SEX" type="java.lang.String"/>

<!-- <many-to-one name="room" column="roomid" class="Room" />-->

<!-- 映射roomid属性作为UserInfo实体与Room实体的关联,其字段在userinfo表中 -->

<property name="roomid" column="roomid" type="java.lang.Long"/>

</class>

</hibernate-mapping>

3 单向关联的实现和问题

下面针对当前Hibernate映射文件给出了具体实现。为了代码完整,先给出Hibernate的配置文件,见例6.10。

例6.10:hibernate.cfg.xml

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE hibernate-configuration PUBLIC

"-//Hibernate/Hibernate Configuration DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<!-- Generated by MyEclipse Hibernate Tools. -->

<hibernate-configuration>

<session-factory>

<!-- 数据库配置和映射文件配置 -->

<property name="connection.username">XXXX</property>

<property name="connection.url">jdbc:oracle:thin:@XXX:1521:XXX</property>

<property name="dialect">org.hibernate.dialect.Oracle9Dialect</property>

<property name="connection.password"> XXXX </property>

<propertyname="connection.driver_class">oracle.jdbc.driver. OracleDriver</property>

<!-- 显示SQL语句 -->

<property name="show_sql">true</property>

<!-- 显示格式化的SQL语句 -->

<property name="format_sql">true</property>

<!-- 显示哪个方法中执行的SQL语句 -->

<property name="use_sql_comments">true</property>

<!-- <mapping resource="Licencedata.hbm.xml" /> -->

<!-- 两个映射文件 -->

<mapping resource="UserInfo.hbm.xml" />

<mapping resource="Room.hbm.xml" />

</session-factory>

</hibernate-configuration>

接着给出的是Hibernate与数据库交互的测试代码,见例6.11。

例6.11:执行插入表

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集合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);

session.flush();

//打印出持久化状态的各实体内容

System.out.println("RoomId:" + room.getId());

System.out.println("Name:" + room.getName());

System.out.println("Roomnumber:" + room.getRoomnumber());

Iterator it = room.getUsers().iterator();

while (it.hasNext()) {

UserInfo userInfoin = (UserInfo)it.next();

System.out.println("UserId:" + userInfoin.getId());

System.out.println("Name:" + userInfoin.getName());

System.out.println("Roomid:" + userInfoin.getRoomid());

System.out.println("Sex:" + userInfoin.getSex());

}

//提交事务

tx.commit();

//关闭Hibernate Session

HibernateSessionFactory.closeSession();

}

由于在Hibernate映射文件中配置了级联(cascade="all"),因此只需要对Room实体进行持久化操作,会关联持久化UserInfo实体。

看起来现在一切都完好了,可惜这里却有一个小小的缺陷。这段代码在不同的前提下会生成两种结果。前提就是:room表与userinfo表是否存在外键关联。

(1)当room表与userinfo表未曾设置外键关联时,那么这段代码就是正确的,其执行后打印出来的SQL语句如下:

Hibernate:

//对room表自增长字段(主键)的最大值获取

select

max(id)

from

room

Hibernate:

//对userinfo表自增长字段(主键)的最大值获取

select

max(id)

from

userinfo

Hibernate:

/* 插入room表 */

insert

into

room

(NAME, roomnumber, id)

values

(?, ?, ?)

Hibernate:

/* 插入userinfo表,此时roomid字段为0 */

insert

into

userinfo

(NAME, SEX, roomid, id)

values

(?, ?, ?, ?)

Hibernate:

/* 通过一对多的关联映射更新userinfo表中当前记录的roomid字段

实现room表与userinfo表的关联 */

update

userinfo

set

roomid=?

where

id=?

关于这段SQL的执行过程见注释。可以看到,Hibernate在处理一对多单向关联时,是通过三句SQL来 完成的。首先是插入主表(room表),然后是插入子表(userinfo表),最后更新子表的关联字段(userinfo表的roomid字段)为主表 的主键(room表的id字段)。

(2)当room表与userinfo表设置外键关联时,那么这段代码就是错误的,其执行后打印出来的SQL语句如下:

Hibernate:

/*插入room表*/

insert

into

room

(NAME, roomnumber, id)

values

(?, ?, ?)

Hibernate:

/*插入userinfo表,此时roomid字段为0*/

insert

into

userinfo

(NAME, SEX, roomid, id)

values

(?, ?, ?, ?)

2007-03-27 13:47:28,822 WARN [org.hibernate.util.JDBCExceptionReporter] - SQL Error: 2291, SQLState: 23000

2007-03-27 13:47:28,822 ERROR [org.hibernate.util.JDBCExceptionReporter] - ORA-02291: 违反完整约束条件 (TEST.FOREIGN) - 未找到父项关键字

和之前没有创建外键关联所不同的是,这里执行了第二句insert SQL后,由于插入roomid的时候插入的是0,而在主表room中没有这样的记录,因此会抛出“未找到父项关键字”的异常。为了解决这个问题,必须在 映射文件上做文章。修改UserInfo.hbm.xml文件,使得其在插入userinfo表时不对roomid进行插入,也即保证该字段是个“外源 性”的字段,见例6.12。

例6.12:修改后的UserInfo.hbm.xml

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http:// hibernate. sourceforge.net/hibernate-mapping-3.0.dtd">

<!-- UserInfo实体的package -->

<hibernate-mapping package="testhibernate">

<!-- UserInfo实体的class和表 -->

<class name="UserInfo" table="userinfo">

<!-- 映射id主键 -->

<id name="id" column="id" type="java.lang.Long">

<!-- 映射为自增长类型 -->

<generator class="increment" />

</id>

<!-- 映射name属性 -->

<property name="name" column="NAME" type="java.lang.String" not-null="true" />

<!-- 映射roomnumber属性 -->

<property name="sex" column="SEX" type="java.lang.String"/>

<!-- <many-to-one name="room" column="roomid" class="Room" />-->

<!-- 映射roomid属性作为UserInfo实体与Room实体的关联,其字段在userinfo表中 -->

<!-- 通过设置insert和update属性为false表示该字段是该外源性的字段,也即该字段的值来源于映射的其他字段 -->

<!-- insert和update属性默认状态为true,在当前操作中只需要设置insert="false"即可 -->

<property name="roomid" column="roomid" type="java.lang.Long" insert="false" update="false"/>

</class>

</hibernate-mapping>

关于修改的结果见注释,对于这样的改动,Hibernate执行的SQL语句如下:

Hibernate:

/*插入room表*/

insert

into

room

(NAME, roomnumber, id)

values

(?, ?, ?)

Hibernate:

/*插入userinfo表,此时由于设置了insert="false",所以roomid字段不被插入*/

insert

into

userinfo

(NAME, SEX, id)

values

(?, ?, ?)

Hibernate:

/*通过一对多的关联映射更新userinfo表中当前记录的roomid字段

实现room表与userinfo表的关联*/

update

userinfo

set

roomid=?

where

id=?

单向关联满足了一定的业务要求,但是当抓取UserInfo实体而又要同时抓取Room实体的业务时就无法被满足。此时就需要进行双向关联的设置。

4 Hibernate的双向关联常规实现

Hibernate的双向关联除了在主表(当前可以看作room表)的映射文件中设置一对多(one-to- many)外,还需要在从表(当前可以看作userinfo表)设置多对一(many-to-one)。首先需要在UserInfo.java实体类中增 加一个Room实体类型的属性,其代码实现见例6.13。

例6.13:增加Room实体类型属性的UserInfo.java

public class UserInfo {

//实体人的主键

private long id;

//人的名字

private String name;

//人的性别

private String sex;

//实体房间的主键(非必需的)

private long roomid;

//实体房间

private Room room;

public Room getRoom() {

return room;

}

public void setRoom(Room room) {

this.room = room;

}

//省略其他get/set方法

}

在保证Room实体的映射不变,即Room.hbm.xml不变的情况下,需要修改UserInfo.hbm.xml来实现多对一。其映射配置见例6.14。

例6.14:多对一配置的UserInfo.hbm.xml

<?xml version='1.0' encoding='UTF-8'?>

<!DOCTYPE hibernate-mapping PUBLIC

"-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate. sourceforge.net/hibernate-mapping-3.0.dtd">

<!-- UserInfo实体的package -->

<hibernate-mapping package="testhibernate">

<!-- UserInfo实体的class和表 -->

<class name="UserInfo" table="userinfo">

<!-- 映射id主键 -->

<id name="id" column="id" type="java.lang.Long">

<!-- 映射为自增长类型 -->

<generator class="increment" />

</id>

<!-- 映射name属性 -->

<property name="name" column="NAME" type="java.lang.String" not-null = "true" />

<!-- 映射roomnumber属性 -->

<property name="sex" column="SEX" type="java.lang.String"/>

<!-- 映射room属性作为UserInfo实体与Room实体的关联,其字段在userinfo表中为roomid,其实体为Room -->

<many-to-one name="room" column="roomid" class="Room" />

</class>

</hibernate-mapping>

完成所有这些之后双向关联就配置完毕。

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 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属性的一对多

<!-- 通过Room实体的users集合属性映射,级联动作为全部 -->

<!-- 将inverse属性设置为true,表示维护动作由UserInfo实体来完成 -->

<set name="users" cascade="all" inverse="true">

<!-- 映射User表的外键roomid -->

<key column="roomid"></key>

<!-- 一对多映射class UserInfo -->

<one-to-many class="UserInfo"></one-to-many>

</set>

其次还需要在实现代码中,将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。

7 结语

单向关联的功能比双向关联要弱,而且单向关联在操作数据库表时总是会执行三句SQL。因此在一般设计和实现中,通常应该优先选择使用双向关联。而使用双向关联时,inverse属性也是不能忽视的一个重点。通过多端来控制外键值的插入是值得推荐的。

分享到:
评论

相关推荐

    hibernate多对多双向关联

    多对多双向关联 &lt;br&gt;注意映射规则: &lt;set name="roles" table="t_user_role"&gt;&lt;br&gt; &lt;key column="userid"/&gt;&lt;br&gt; &lt;many-to-many class="com.bjsxt.hibernate.Role" column="roleid"/&gt; &lt;/set&gt;&lt;br&gt; table...

    Hibernate_Annotation关联映射

    如果是双向关联,其中一段必须定义为Owner,另一端必须定义为inverse(在对关联表进行更新操作时这一端将被忽略) @Entity() public class Employer implements Serializable { private Integer id; private ...

    hibernate学习5之one-to-many双向关联.docx

    如果在”一“一端维护一对多关联关系,hibernate会发出多余的udpate语句,所以我们一般在多的一端来维护关联关系。 加上inverse="true"这个属性,就可以强制在多的一端维护关系了。

    精通hibernate:对象持久化技术孙卫琴第二版part2

    7.2 映射一对多双向关联关系 156 7.2.1 [set]元素的inverse属性 161 7.2.2 级联删除 163 7.2.3 父子关系 164 7.3 映射一对多双向自身关联关系 165 7.4 改进持久化类 171 7.5 小结 175 7.6 思考题 176 第8章 ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part2

     7.2 映射一对多双向关联关系  7.2.1 元素的inverse属性  7.2.2 级联删除  7.2.3 父子关系  7.3 映射一对多双向自身关联关系  7.4 改进持久化类  7.5 小结  7.6 思考题 第8章 通过Hibernate操纵对象(上) ...

    Hibernate注释大全收藏

    这种策略支持双向的一对多关联,但不支持 IDENTIFY 生成器策略,因为ID必须在多个表间共享。一旦使用就不能使用AUTO和IDENTIFY生成器。 每个类层次结构一张表 @Entity @Inheritance(strategy=InheritanceType....

    精通Hibernate:对象持久化技术第二版part3

    7.2 映射一对多双向关联关系 156 7.2.1 [set]元素的inverse属性 161 7.2.2 级联删除 163 7.2.3 父子关系 164 7.3 映射一对多双向自身关联关系 165 7.4 改进持久化类 171 7.5 小结 175 7.6 思考题 176 第8章 ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part4

     7.2 映射一对多双向关联关系  7.2.1 元素的inverse属性  7.2.2 级联删除  7.2.3 父子关系  7.3 映射一对多双向自身关联关系  7.4 改进持久化类  7.5 小结  7.6 思考题 第8章 通过Hibernate操纵对象(上) ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part3

     7.2 映射一对多双向关联关系  7.2.1 元素的inverse属性  7.2.2 级联删除  7.2.3 父子关系  7.3 映射一对多双向自身关联关系  7.4 改进持久化类  7.5 小结  7.6 思考题 第8章 通过Hibernate操纵对象(上) ...

    精通 Hibernate:Java 对象持久化技术详解(第2版).part1.rar

     7.2 映射一对多双向关联关系  7.2.1 元素的inverse属性  7.2.2 级联删除  7.2.3 父子关系  7.3 映射一对多双向自身关联关系  7.4 改进持久化类  7.5 小结  7.6 思考题 第8章 通过Hibernate操纵对象(上) ...

    Hibernate开发租房系统2 源码

    使用cascade和inverse优化区和街道关联关系 实现区和街道双向一对多关联关系

    Hibernate开发指南

    单向一对多关系......................................................39 ÿ 双向一对多关系......................................................44 多对多关联........................................

    jdbc基础和参考

    一般在做双向多对一(一对多)关联关系映射的时候,一般会设置让一的一方放弃对关联关系的维护,以减少不必要的更新语句 一对一: 基于外键的一对一 Wife Husband id id name name h_id references Husband...

    最新Java面试宝典pdf版

    12、写Hibernate的一对多和多对一双向关联的orm配置? 122 9、hibernate的inverse属性的作用? 122 13、在DAO中如何体现DAO设计模式? 123 14、spring+Hibernate中委托方案怎么配置? 123 15、spring+Hibernate中委托...

    Java面试宝典2010版

    12、写Hibernate的一对多和多对一双向关联的orm配置? 9、hibernate的inverse属性的作用? 13、在DAO中如何体现DAO设计模式? 14、spring+Hibernate中委托方案怎么配置? 15、spring+Hibernate中委托方案怎么配置? ...

    Java面试笔试资料大全

    12、写Hibernate的一对多和多对一双向关联的orm配置? 122 9、hibernate的inverse属性的作用? 122 13、在DAO中如何体现DAO设计模式? 123 14、spring+Hibernate中委托方案怎么配置? 123 15、spring+Hibernate中委托...

    JAVA面试宝典2010

    12、写Hibernate的一对多和多对一双向关联的orm配置? 122 9、hibernate的inverse属性的作用? 122 13、在DAO中如何体现DAO设计模式? 123 14、spring+Hibernate中委托方案怎么配置? 123 15、spring+Hibernate中委托...

    Java面试宝典-经典

    12、写Hibernate的一对多和多对一双向关联的orm配置? 122 9、hibernate的inverse属性的作用? 122 13、在DAO中如何体现DAO设计模式? 123 14、spring+Hibernate中委托方案怎么配置? 123 15、spring+Hibernate中委托...

Global site tag (gtag.js) - Google Analytics