- 浏览: 72088 次
- 性别:
- 来自: 合肥
文章分类
最新评论
-
yeliping2011:
赞一个
forward和redirect区别 -
xia253964715:
能给我一个spring.jar包吗?
xia_wangyan@ ...
spring 2.0 包说明 -
dhaigang:
谢谢楼主,不知楼主是从哪学到的
spring 2.0 包说明 -
zhang19841017:
<div class='quote_title'> ...
findbugs插件 -
fastzch:
不知所云。
hibernate标识符问题
6.2 问题:Hibernate单、双向关联与inverse属性
6.2.1 Hibernate的单、双向关联http://book.csdn.net/bookfiles/563/10056318684.shtml
设计师L并不理解在Hibernate中单向关联与双向关联有什么区别。于是他也就无法告诉开发人员,在配置实体间关系时究竟如何配置。从Hibernate的文档中来看,官方并没有特别告诉使用者哪种关联方式可靠。而且从L的实验结果来看,通常的一些行为使用单向关联与双向关联的结果一样。那么究竟该如何看待这两种关联方式呢?
6.2.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>
6.2.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实体的业务时就无法被满足。此时就需要进行双向关联的设置。
6.2.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>
完成所有这些之后双向关联就配置完毕。
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属性的一对多
<!-- 通过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。
6.2.7 结语
单向关联的功能比双向关联要弱,而且单向关联在操作数据库表时总是会执行三句SQL。因此在一般设计和实现中,通常应该优先选择使用双向关联。而使用双向关联时,inverse属性也是不能忽视的一个重点。通过多端来控制外键值的插入是值得推荐的。
6.2.1 Hibernate的单、双向关联http://book.csdn.net/bookfiles/563/10056318684.shtml
设计师L并不理解在Hibernate中单向关联与双向关联有什么区别。于是他也就无法告诉开发人员,在配置实体间关系时究竟如何配置。从Hibernate的文档中来看,官方并没有特别告诉使用者哪种关联方式可靠。而且从L的实验结果来看,通常的一些行为使用单向关联与双向关联的结果一样。那么究竟该如何看待这两种关联方式呢?
6.2.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>
6.2.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实体的业务时就无法被满足。此时就需要进行双向关联的设置。
6.2.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>
完成所有这些之后双向关联就配置完毕。
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属性的一对多
<!-- 通过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。
6.2.7 结语
单向关联的功能比双向关联要弱,而且单向关联在操作数据库表时总是会执行三句SQL。因此在一般设计和实现中,通常应该优先选择使用双向关联。而使用双向关联时,inverse属性也是不能忽视的一个重点。通过多端来控制外键值的插入是值得推荐的。
发表评论
-
current_session_context_class thread
2009-03-11 18:09 32421. 如果想让spring帮你管理事务,只能在spring中配 ... -
hibernate标识符问题
2008-07-28 10:57 1220转http://lz726.iteye.com/blog/11 ... -
Hibernate3.0 包说明
2008-05-29 09:14 1406asm.jar: ASM is an all purpose ... -
Hibernate中二级缓存的建立
2008-05-12 09:51 8661、在hbm.xml中增加<cache>声明。 ... -
Hibernate实体状态
2007-11-27 18:12 16096.1 问题:Hibernate中的 ... -
HibernateTool插件说明
2007-11-22 16:58 1440插件地址:http://download.jboss.org/ ... -
Hibernate性能分析点滴
2007-11-19 17:59 2089hibernate.jdbc.batch_size 这个属性 ...
相关推荐
多对多双向关联 <br>注意映射规则: <set name="roles" table="t_user_role"><br> <key column="userid"/><br> <many-to-many class="com.bjsxt.hibernate.Role" column="roleid"/> </set><br> table...
`Inverse`属性主要用在双向关联中,用于指定哪个关联端负责管理关联关系的维护。默认情况下,双向关联的两端都是“负责端”,即两者都会在持久化操作时更新关联。但这样可能会导致数据冗余和一致性问题。 五、`...
在Hibernate的一对多关联中,双向关联意味着双方都可以通过导航属性访问对方。例如,用户类(User)可以有一个订单集合(Orders),而订单类(Order)同样有一个用户属性(User)。这样设计的好处在于,我们可以在任一侧轻松...
为了使双向关联正常工作,还需要在Java代码中进行相应的设置。例如,在创建新订单时,不仅要设置订单的用户属性,也要在用户对象的订单集合中添加该订单: ```java User user = new User(); Order order = new ...
在一个双向关联中,如果一方设为inverse="true",意味着这一方不负责维护关联关系。以用户和订单为例,如果User对象设置为inverse,那么更新Order对象的User引用时,Hibernate不会更新数据库中的关联关系。相反,...
理解JPA中一对一双向关联的实现,你可以查看Hibernate(一个流行的JPA实现)的源码,学习其内部如何处理关联的建立和维护。同时,使用诸如IntelliJ IDEA这样的集成开发环境(IDE),其内置的代码生成器可以帮助你...
标题"Hibernate双向一对多"指的是Hibernate框架中的一个重要关系映射概念,即在一个实体类中,一个实例可以与多个另一个实体类的实例相关联,而在另一个实体类中,每个实例也可以关联到该实体类的一个实例。...
### Hibernate关联映射的作用与常用属性详解 #### 关联映射概述 在对象关系映射(Object Relational Mapping,简称ORM)技术中,Hibernate作为Java领域内非常成熟且功能强大的框架之一,它允许开发者将Java类映射...
本篇文章将深入探讨 Hibernate 中的一对多双向关联映射。 在数据库设计中,一对多关联是指一个实体可以与多个其他实体相关联,比如一个学生可以有多个课程,一个部门可以有多名员工。在Hibernate中,这种关系可以...
如果是双向关联,其中一段必须定义为Owner,另一端必须定义为inverse(在对关联表进行更新操作时这一端将被忽略) @Entity() public class Employer implements Serializable { private Integer id; private ...
总之,理解和熟练运用Hibernate的一对多、多对一以及双向关联,是提升Java企业级应用开发效率的关键。通过注解或XML配置,开发者可以灵活地定义实体间的关联,实现数据的持久化。同时,注意级联操作、懒加载策略等...
除了单向关联,还可以建立双向关联,例如在学生类中添加对班级的引用。在映射文件中使用`<many-to-one>`标签定义。 6. 多对多关联(Many-to-Many): 如课程与教师之间的关系,通常需要通过一个中间表来实现。...
本主题将深入探讨使用Hibernate进行多对多双向关联的实现,既可以通过注解(Annotation)方式,也可以通过XML配置文件来完成。 首先,我们来看多对多关联的基本概念。在数据库设计中,当两个实体之间存在多个实例...
在Hibernate框架中,双向一对多关联映射是常见的对象关系映射(ORM)方式,用于在Java对象模型中表示数据库中的两个实体之间的多对一关系。在这个场景中,"一"端通常指的是一个实体可以拥有多个另一个实体的实例,而...
`inverse="true"`属性指明了该关系是由另一端维护的,这在双向关联中很重要,因为它决定了关系的维护方向,防止在两个实体间形成循环依赖。 ### 三、逆向关联的理解 在一对多双向关联中,通常会有一个实体负责维护...
- **双向关联**:在两个实体的映射文件中都需要`<many-to-many>`标签,并且需要一个共享的中间表。 关联映射的配置涉及到`cascade`属性,它决定了级联操作的行为,如`save-update`、`delete`等。`inverse`属性则...
在Hibernate中,`inverse`属性通常与双向关联的实体一起使用,它决定了哪一方实体在进行删除或更新关联时拥有控制权。例如,在一对多关系中,如果`inverse`属性被设置为`true`,那么这一方将不会主动触发对关联实体...
双向关联映射指的是双方都定义了关联,这样可以更好地反映现实世界中的关系,同时也便于数据的检索。 ###### 2.2.1 一对多 (1-N) - **无连接表**:一方使用集合形式访问多方,而另一方则包含一个引用到这一方的...
7. **反向关联(Inverse)**:在双向关联中,一方称为拥有端(owning side),另一方称为无主端(inverse side)。无主端的关联不会影响数据库操作,所有操作都由拥有端控制。通过@JoinColumn的nullable属性和...
本篇将详细讲解如何在Hibernate中实现一对多的双向关联,并提供相关的代码示例。 在关系型数据库中,一对多关联是指一个表的记录可以对应另一个表的多个记录。在Hibernate中,这种关系可以通过在实体类和映射文件中...