关于Hibernate的关联关系及其操作
【IT168 技术文档】
一、 什么是关联关系以及进行关联操作的好处
关联关系表示不同类的对象之间的结构关系,它在一段时间内将多个类的实例连接在一起(这与依赖关系不同,依赖关系表示两个实例之间的临时关联关系)。 您可以使用关联关系表示对象了解其他对象,有时,对象必须相互引用才能实现交互,例如互相发送消息。
在Hibernate中关联关系表现在所映射的表与表之间的关系,采用关联操作,能够使有关系的表之间保持数据的同步,同时,关联操作能够使程序员在编写程序过程中,减少对多表操作代码的编写,并且优化了程序,提高了程序运行的效率。
二、 关联关系的类型、映射文件
以下开始介绍在Hibernate中关联关系的类型,以及每种类型的映射文件:
1、 一对一关联
假设有两个实体,分别是客户对象(Customer)和客户身份对象(Certificate),它们之间就是一对一的关系。一个客户只能有一个身份,一个身份也只能对应一个客户。该一对一关联可以分为两种,分别是以主键关联和以外键关联。如图1-1是类关系图:

a.以主键关联:所谓以主键关联,是指两张数据表通过主键取得一对一的关联关系(其中一张表的主键是引用另一张表主键的外键),两张表的主键值必须相同。如图1-2:

图1-2
【IT168 技术文档】
下面是以主键关联的Customer和Certificate的映射文件:
● Customer.hbm.xml:
● Certificate.hbm.xml:<hibernate-mapping>
<class name="hibernate.Customer" table="customer" schema="dbo" catalog="test">
<id name="id" type="java.lang.Long">
<column name="id" />
<generator class="uuid.hex" />
</id>
<!--映射对应的身份对象-->
<one-to-one name="Certificate" class="hibernate.Certificate"
update="true" insert="true" fetch="select" cascade="all"/><property name="name" type="java.lang.String">
<column name="name" length="50" not-null="true" />
</property>
</class>
</hibernate-mapping>
b.以外键关联:两张表的主键值不同,它使用一个新添加的字段来作为外键维持一对一关系。如图1-3:<hibernate-mapping>
<class name="hibernate.Certificate" table="certificate" schema="dbo" catalog="test">
<id name="id" type="java.lang.Long">
<column name="id" />
<!—引用Customer的主键作为Certificate的主键和外键-->
<generator class="foreign" />
<param name="property">Customer</param>
</generator>
</id>
<property name="information" type="java.lang.String">
<column name="information" length="50" not-null="true" />
</property>
<one-to-one name="Customer" class="hibernate.Customer"
constrained="true"<!—表示Certificate引用了Customer主键作外键-->
</class>
</hibernate-mapping>

图1-3
【IT168 技术文档】
下面是以外键关联的Customer和Certificate的映射文件:
● Customer.hbm.xml是同以主键关联中的Customer映射文件一样。
● Certificate.hbm.xml:
以外键关联的一对一,其实本质上变成了一对多的双向关联,编写时直接按照一对多和多对一的要求编写,最后在many-to-one这一边加上一个uique="true",说明它是一对多的特例。<hibernate-mapping>
<class name="hibernate.Certificate" table="certificate" schema="dbo" catalog="test">
<id name="id" type="java.lang.Long">
<column name="id" />
<generator class="uuid.hex" />
<param name="property">Customer</param>
</generator>
</id>
<property name="information" type="java.lang.String">
<column name="information" length="50" not-null="true" />
</property>
<many-to-one name="Customer" class="hibernate.Customer"
<!--uique设为true,表示由多对一变成一对一-->
uique="true" column="customer_id"
</class>
</hibernate-mapping>
one-to-one 节点有以下属性:
cascade 操作级联(cascade)关系,可选值有all:所有情况下均进行级联操作;none:所有情况下均不进行级联操作;save-update:在执行save-update时进行级联操作。delete:在执行delete时进行级联操作。级联(cascade)在Hibernate映射关系中是个非常重要的概念。它指的是当主控方执行操作时,关联对象(被动方)是否同步执行同一操作。如对主控对象调用save-update或delete方法时,是否同时对关联对象(被动方)进行save-update或delete。这里,当客户(Customer)被更新或者删除时,其所关联的订单(Order)同时被修改或者删除,因此,这里的级联关系设置为all。
constrained 约束表明主控表的主键上是否存在一个外键(foreign key)对其进行约束。这个选项关系到save、delete等方法的级联操作顺序。默认为关联类的主键属性名。
【IT168 技术文档】
2、 一对多关联/多对一关联
一对多和多对一关联分为单向关联和双向关联,在此,着重介绍双向关联。假设有两个实体,客户对象(Customer)和订单对象(Order),一个客户可以有多个订单,表现为一对多的关系,一个订单只能属于一个客户,表现为多对一的关系,如图2-1:

图2-1
下面是Customer和Order所对应的映射文件:
● Customer.hbm.xml:
<hibernate-mapping>
<class name="hibernate.Customer" table="customer" schema="dbo" catalog="test">
<id name="id" type="java.lang.Long">
<column name="id" />
<generator class="uuid.hex" />
</id>
<property name="name" type="java.lang.String">
<column name="name" length="50" not-null="true" />
</property>
<set name="Orders" inverse="true" cascade="save-update">
<key>
<column name="customer_id" not-null="true" />
</key>
<one-to-many class="hibernate.Order" />
</set>
</class>
</hibernate-mapping>
● Order.hbm.xml:
对于<many-to-one>标签具有column属性,column属性总是和关联类的主键id相对应(对于Order来说,关联类是Customer),因此Hibernate会根据order表中的customer_id以及customer表中设置的id列,取出order.customer_id=customer.id的记录,构造成Customer对象返回给Order类。<hibernate-mapping>
<class name="hibernate.Order" table="order" schema="dbo" catalog="test">
<id name="id" type="java.lang.Long">
<column name="id" />
<generator class="uuid.hex" />
</id>
<many-to-one name="Customer" class="hibernate.Customer" fetch="select">
<column name="customer_id" not-null="true" />
</many-to-one>
<property name="orderName" type="java.lang.String">
<column name="order_name" length="50" not-null="true" />
</property>
</class>
</hibernate-mapping>
Customer对Order是一对多的关系。一对多具有<key column>属性,key标签总是和本身类(在此本身类是Customer)的主键值相对应。在取得Customer对象时,通过Customer的主键id与Order中的customer_id进行匹配,如果customer.id=order.customer_id,则认为此订单是本客户的,于是加载此学生,把学生对象加入Customer对象的订单集合中。
one-to-many 节点有以下属性:lazy 是否采用延迟加载。inverse 用于标识双向关联中的被动方一端,inverse=false的一方(主控方)负责维护关联关系,默认值 为false。cascade 操作级联(cascade)关系,可选值为all : 所有情况下均进行级联操作;none:所有情况下均不进行级联操作;save-update:在执行save-update时进行级联操作。
【IT168 技术文档】
3、 多对多关联
Hibernate关联关系中相对比较特殊的就是多对多关联,多对多关联与一对一关联和一对多关联不同,多对多关联需要另外一张映射表用于保存多对多映射信息。由于多对多关联的性能不佳(由于引入了中间表,一次读取操作需要反复数次查询),因此在设计中应该避免大量使用。同时,在对多对关系中,应根据情况,采取延迟加载机制来避免无谓的性能开销。多对多关联只有双向关联,假如有订单对象(Order)和产品对象(Product),一个订单可以对应多个产品,一个产品同样也可以对应多个订单,订单和产品的关系就属于多对多关联关系。为表示它们之间的关系,还需建立一张订单—产品关系表(Order_Product),这张表里只存储order表和product表的关联关系,并没有额外的内容。这三者关系如图3-1:

图3-1
下面是Order和Product所对应的映射文件:
● Order.hbm.xml:
<hibernate-mapping>
<class name="hibernate.Order" table="order" schema="dbo" catalog="test">
<id name="id" type="java.lang.Long">
<column name="id" />
<generator class="uuid.hex" />
</id>
<property name="orderName" type="java.lang.String">
<column name="order_name" length="50" not-null="true" />
</property>
<set name="product" table="order_product"
cascade="all">
<key column="order_id"/>
<many-to-many class="hibernate.Order"
Column="product_id"/>
</set>
</class>
</hibernate-mapping>
● Product.hbm.xml:
<hibernate-mapping>
<class name="hibernate.Product" table="product" schema="dbo" catalog="test">
<id name="id" type="java.lang.Long">
<column name="id" />
<generator class="uuid.hex" />
</id>
<property name="productName" type="java.lang.String">
<column name="product_name" length="50" not-null="true" />
</property>
<set name="order" table="order_product" inverse="true"
cascade="save_update">
<key>
<column name="product_id" not-null="true" />
</key>
<many-to-many class="hibernate.Order" column="order_id"/>
</set>
</class>
</hibernate-mapping>
一般情况下,cascade应该设置为“save-update”,对于多对多逻辑而言,很少出现删除一方需要级联删除所有关联数据的情况,如删除一个Order,一般不会删除其中包含的Product(这些Product 可能还被其他的Order所引用)。反之删除Product一般也不会删除其所关联的所有Order。
【IT168 技术文档】
三、 对关联关系的对象进行数据库操作
对关联关系的对象进行数据库操作包括级联保存和更新,级联删除以及级联检索,在次,仅以一对多的双向关联为例进行阐述。假设有客户类(Customer类)和订单类(Order类),根据需求建立一对多的双向关联关系,即:一个客户对应多个订单的双向关联关系。
关于Customer和Order的映射文件,在上述一对多/多对一的关系中已经给出,下面代码是Customer映射文件中<set>元素:
<set>元素包括以下属性:<set name="Orders" inverse="true" cascade="save-update">
<key>
<column name="customer_id" not-null="true" />
</key>
<one-to-many class="hibernate.Order" />
</set>
name:设定待映射的持久化类的属性名,这里为Customer类的Orders属性;
cascade:当取值为“save-update”,表示级联保存和更新;
<set>元素还包含两个子元素:<key>和<one-to-many>,其中<key>元素设定与所关联的持久化类对应的表的外键,此处为order表的customer_id字段;<one-to-many>元素设定所关联的持久化类,此处为Order类。
下面列出级联保存和更新、级联删除、级联检索的方法:
级联保存和更新:当设置了cascade属性为“save-update”之后,Hibernate在持久化Customer对象时,会自动持久化所关联的Order对象。代码如下:
级联删除:当cascade属性设为默认值“none”,Hibernate删除一个持久化对象时,不会自动删除与它关联的其他持久化对象,所以在采用级联删除时,可以把cascade设为“all”或者“delete”,这样,Hibernate在删除Customer对象的同时就会自动删除与此关联的Order对象。代码如下:tx=session.beginTransaction();
Customer customer=new Customer();
Order order=new Order();
customer.setName("a");
order.setOrderName("aa");
//建立Customer对象和Order对象的一对多双向关联关系
order.setCustomer(customer);
customer.getOrders().add(order);
session.save(customer);
tx.commit();
在<set>元素还有一个属性inverse,如果将inverse属性值设为“true”,
Hibernate会按照Order对象的状态变化同步更新Customer。代码如下:tx=session.beginTransaction();
//分别加载Customer和Order对象
Customer customer=(Customer)session.get(Customer.class,new
Long(1));
Order order=(Order)session.get(Order.class,new Long(2));
//建立Order和Customer关联关系
order.setCustomer(customer);
session.save(order);
tx.commit();
级联检索:在映射文件中,用<set>元素来配置一对多关联关系,其中在<set>元素中有个lazy和outer-join属性,当lazy属性值设置为false时,表示采用立即检索,当lazy属性值设置为true时,表示采用延迟检索;当outer-join属性值设置为false,表示不采用迫切左外连接检索,如果outer-join属性值设置为true,表示采用迫切左外连接检索方式。tx=session.beginTransaction();
Customer customer=(Customer)session.get(Customer.class,new Long(1));
session.delete(customer);
tx.commit();
● 立即检索:代码如下:
执行session的get()方法时,对于Customer对象采用类级别的立即检索策略,对于与Customer关联的Order对象,采用一对多关联级别的立即检索策略,因此Hibernate执行以下select语句:tx=session.beginTransaction();
Customer customer=(Customer)session.get(Customer.class,new Long(1));
Set orders=customer.getOrders();
Iterator orderIterator=orders.iterator();
tx.commit();
通过以上select语句,Hibernate加载了一个Customer对象和两个Order对象。Customer对象的orders属性引用的是一个Hibernate提供的Set代理类实例,这个代理类实例引用两个Order对象。select * from Customer where id=1;
select * from Order where customer_id=1;
● 延迟检索:假如当一个客户有100个订单,session的get()方法会立即加载一个Customer对象和100个Order对象,但是在很多情况下,应用程序并不需要访问这些Order对象,为此,可以采用延迟检索方式进行检索。此时,应在<set>元素中设置lazy属性值为“true”,代码与立即检索一样,但是程序在执行时却有所区别。此时运行session的get ()方法时,仅立即检索Customer对象,执行如下的select语句:
select * from Customer where id=1;
但是此时Hibernate并没有创建Order代理类实例,get()方法返回的Customer对象的orders属性引用的是Hibernate提供的集合代理类实例,当应用程序第一次访问它,例如调用orders.iterator()方法时,Hibernate会初始化这个集合代理类实例,在初始化过程中到数据库中检索所有与Customer关联的Order对象,执行如下的select语句:
select * from Order where customer_id=1;
采用这种检索方式,便可以在需要的时候才去检索出相应的Order对象,不必要的时候就不用去获得Order对象,大大地节省了资源,也提高了程序的运行效率。
四、 总结:
对象关联关系有一对一、多对一、一对多和多对多。其中一对一关系有两种方式:以主键关联和以外键关联;多对一和一对多关系比较常见,而且分为单向关联和双向关联,通常设置为双向关联,并在“一”这一方设置inverse=“true”(比如上述例子中的Customer);多对多关联需要第三章表来保存两个实体之间的关系。 Hibernate检索策略有立即检索、延迟检索等,对于一对多和多对多的关联,一般设置为延迟检索。
在关联关系中,对象所生成的映射文件中,有<set>元素的各个属性设置。其中cascade属性值包括“none”、“all”等,当cascade设置为“none”表示对一个对象的操作不会影响到相关联的其他对象,当设置为“all”表示对一个对象的所有操作(增加、删除、修改)都会引起相关联的其他对象的操作(增加、删除、修改)。另外还有inverse属性值,它的值设置为“true”表示双方关系仅有对方去维持,一般在一对多的双向关联或者在多对多的关联中使用。
最后,在Hibernate中使用关联关系时,一定要对每一种类型的关联关系写好相对应的映射文件,设置好相应的属性值,建立好对象之间的关系,才能更好的对它们进行数据库的操作。