关联关系分:单向关联(一对多、多对一)和双向关联(一对多双向)
在关系数据库中,只存在外键参照关系,而且总是由“many”方参照“one”方,因为这样才能消除数据冗余,因此关系数据库实际上只支持多对一或一对一的单项关联。
1、单向关联及级联保存和更新
Order 和 Customer存在多对一的关系,在Order映射文件中可以设置为:
<many-to-one
name="customer"
column="CUSTOMER_ID"
class="mypack.Customer"
not-null="true"/>
存在这样的使用方法:
Customer customer = new Customer("Jack");
//session.save(customer);
Order order1=new Order("Jack_Order001",customer);
Order order2=new Order("Jack_Order002",customer);
session.save(order1);//抛出PropertyValueException异常
session.save(order2);
抛出异常的原因:
save(order1)时,因为customer没有被持久化(在这种设置情况下,HIbernate不会自动持久化Customer对象),所以对应的customerId=null,当保存时<many-to-one>设置 customer中的customerId为not-null,所以会出现异常。如果设置not-null="false",也会抛出异常:TransientObjectException,原因是order1的customer属性引用了一个临时对象Customer。
如果使用级联保存和更新那么可以解决这个问题:
<many-to-one
name="customer"
column="CUSTOMER_ID"
class="mypack.Customer"
cascade="save-update"
not-null="true"/>
这样Hibernate会自动持久化关联的临时对象。
2、映射一对多双向关联
既然是双向关联,实际上一对多还是多对一都是一回事,只是一对多比较顺口!
1中在Order中有了customer属性(多对一关系),可以直接在Order对象中获得所属的Customer,如果想知道该order所属的Customer的所有order,那么还需要通过一次查询才能获得。如果在Customer中添加Customer对Order的一对多关系,设为orders属性,那么就可以方便实现上述功能,调用getOrders()方法即可,这就形成了双向关联。
注:Hibernate要求在持久化类中定义集合类属性时,必须把属性声明为接口类型,如:java.util.set、java.util.Map、java.util.List。这样可以提高持久化类的透明性。
在定义集合属性时,通常把他初始化为集合实现类的一个实例,private Set orders = new HashSet();这样可以提高程序的健壮性,避免因为null而抛出异常。
映射方法:
<set name="orders" cascade="save-update">
<key column="CUSTOMER_ID"/>
<one-to-many class="myapck.Order"/>
</set>
当建立order对象和customer对象的双向关联关系时,需要在程序中同时修改两个对象的属性:
-建立Order到Customer对象的多对一关系
order.setCustomer(customer)
hibernate探测到这个变化后会将order对应的CUSTOMER_ID的值改为customer的id,会执行SQL:
update ORDERS set ORDER_NUMBER="Jack_Order001",CUSTOMER_ID=2 where ID=2;
-建立Customer对象到Order对象的一对多关联关系:
customer.getOrders().addOrder(order);
hibernate探测到这个变化后也会将order对应的CUSTOMER_ID的值改为customer的id,会执行SQL:
update ORDERS set CUSTOMER_ID=2 where ID=2;
这样会重复执行多余的SQL,影响性能。解决这一问题的方法是把<set>元素的inverse属性设置为true,默认为false;
<set name="orders" cascade="save-update" inverse="true">
<key column="CUSTOMER_ID"/>
<one-to-many class="myapck.Order"/>
</set>
这样设置以后,如果只执行:order.setCustomer(customer),同样会自动更新order记录
但是如果只执行:customer.getOrders().addOrder(order);却不会自动更新order记录,这是因为在Customer对象的orders属性映射中<set>元素设置了inverse = true;所以hibernate不会同步更新数据库:
结论:
--在映射一对多的双向关联关系时,应该在“many”方(orders属性)把inverse属性设置为"true",这样可以提高应用的性能。
--在建立两个对象的双向关联时,应该同时修改关联两端的对象的相应属性:
customer.getOrders().addOrder(order);
order.setCustomer(customer)
同理,解除双向关联的关系时,也应该修改关联两端的对象的属性:
customer.getOrders().remove(order);
order.setCustomer(null);
------------------------------------------------------------------------------------------------------------
多对一(many-to-one)
通过many-to-one
元素,可以定义一种常见的与另一个持久化类的关联。 这种关系模型是多对一关联(实际上是一个对象引用-译注):这个表的一个外键引用目标表的 主键字段。
<many-to-one name="propertyName" column="column_name" class="ClassName" cascade="cascade_style" fetch="join|select" update="true|false" insert="true|false" property-ref="propertyNameFromAssociatedClass" access="field|property|ClassName" unique="true|false" not-null="true|false" optimistic-lock="true|false" lazy="proxy|no-proxy|false" not-found="ignore|exception" entity-name="EntityName" formula="arbitrary SQL expression" node="element-name|@attribute-name|element/@attribute|." embed-xml="true|false" index="index_name" unique_key="unique_key_id" foreign-key="foreign_key_name" />
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
cascade
属性设置为除了none
以外任何有意义的值, 它将把特定的操作传递到关联对象中。这个值就代表着Hibernate基本操作的名称, persist, merge, delete, save-update, evict, replicate, lock, refresh
, 以及特别的值delete-orphan
和all
,并且可以用逗号分隔符 来组合这些操作,例如,cascade="persist,merge,evict"
或 cascade="all,delete-orphan"
。更全面的解释请参考第 10.11 节 “传播性持久化(transitive persistence)”. 注意,单值关联 (many-to-one 和 one-to-one 关联) 不支持删除孤儿(orphan delete,删除不再被引用的值).
一个典型的简单many-to-one
定义例子:
<many-to-one name="product" class="Product" column="PRODUCT_ID"/>
property-ref
属性只应该用来对付遗留下来的数据库系统, 可能有外键指向对方关联表的是个非主键字段(但是应该是一个惟一关键字)的情况下。 这是一种十分丑陋的关系模型。比如说,假设Product
类有一个惟一的序列号, 它并不是主键。(unique
属性控制Hibernate通过SchemaExport工具进行的DDL生成。)
<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>
那么关于OrderItem
的映射可能是:
<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>
当然,我们决不鼓励这种用法。
如果被引用的唯一主键由关联实体的多个属性组成,你应该在名称为<properties>
的元素 里面映射所有关联的属性。
假若被引用的唯一主键是组件的属性,你可以指定属性路径:
<many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/>
------------------------------------------------------------------------------------------------------------
3、级联删除
如果在删除customer的时候,将其关联的orders也删除只需要设置cascade=delete
<set name="orders" casecade="delete" inverse="true">
<key column="CUSTOMER_ID"/>
<one-to-many class="myapck.Order"/>
</set>
4、父子关系
解除customer与其中一个order之间的关系:
tx=session.beginTransaction();
Customer customer=(Customer>session.load(Customer.class,new Long(2));
Order order = (Order)customer.getOrders().iterator().next();
customer.getOrders().remove(order);
order.setCustover(null);
tx.commit();
如果cascade取默认值“none”,当order.setCustomer(null)时,会执行:
update ORDERS set CUSTOMER_ID = null where ID=2;
如果希望Hiernate自动删除不再和customer对象关联的order对象,可以把cascade属性设置为"all-delete-orphan"
<set name="orders" cascade="all-delete-orphan" inverse="true">
<key column="CUSTOMER_ID"/>
<one-to-many class="myapck.Order"/>
</set>
此时如果执行order.setCustomer(null)会执行一下SQL:
delete from ORDERS where CUSTOMER_ID=2 and ID=2;
cascade="all-delete-orphan"时Hibernate会按以下方式处理Customer对象:
--当保存或更新Customer对象时,级联保存或更新所有关联的Order对象,相当于cascade="save-update"
--当删除Customer对象时,级联删除所有关联的Order对象,相当于cascade="delete"
--删除不再和Customer对象关联的所有Order对象。
总结:
当关联双方存在父子方系,就可以把父方的cascade属性设置为"all-delete-orphan"。所谓父子关系,是指由父方来控制子方的持久化生命周期,子方对象必须和一个父方对象关联。如果删除父方对象,应该级联删除所有的关联子方对象,如果一个子方对象不再和一个父方对象关联,应该把这个子方对象删除。
5、一对多双向自身关联关系
考虑以下情形:Category表
每个Category可能有其(0或1个)父类和(多个子类),其映射文件如下:
<! DOCTYPE hibernate-mapping
PUBLIC "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd" >
< hibernate-mapping >
< class name ="mypack.Category" table ="CATEGORIES" >
< id name ="id" type ="long" column ="ID" >
< generator class ="increment" />
</ id >
< property name ="name" type ="string" >
< column name ="NAME" length ="15" />
</ property >
< set
name ="childCategories"
cascade ="save-update"
inverse ="true"
>
< key column ="CATEGORY_ID" />
< one-to-many class ="mypack.Category" />
</ set >
< many-to-one
name ="parentCategory"
column ="CATEGORY_ID"
class ="mypack.Category"
/>
</ class >
</ hibernate-mapping >
表数据:
父类别的映射为:
<many-to-one
name="parentCategory"
column="CATEGORY_ID"
class="mypack.Category"
/>
原理:
<many-to-one>表示多个该类对象对应一个父类别。[以外键匹配主键]
<many-to-one>元素表示以"CATEGORY_ID"作为外键与"mypack.Category"对应的表的主键去匹配,如果存在则是父类别。
如:表中的food,CATEGORY_ID=null所以没有父类别,与图相符,vegetable和fruit的父类是food;
子类别的映射:
<set
name="childCategories"
cascade="save-update"
inverse="true"
>
<key column="CATEGORY_ID" />
<one-to-many class="mypack.Category" />
</set>
原理:
<one-to-many>表示单个该类对象对应多个子类别;[以主键去匹配外键]
<one-to-many>元素表示以该记录对应的表的主键(ID)去匹配"mypack.Category"对应的表中的"CATEGORY_ID"字段;