精通hibernate学习笔记(3)[关联关系]
关联关系分:单向关联(一对多、多对一)和双向关联(一对多双向)
在关系数据库中,只存在外键参照关系,而且总是由“many”方参照“one”方,因为这样才能消除数据冗余,因此关系数据库实际上只支持多对一或一对一的单项关联。
1、单向关联及级联保存和更新
Order和Customer存在多对一的关系,在Order映射文件中可以设置为:
<many-to-one
name="customer"
column="CUSTOMER_ID"
class="mypack.Customer"
not-null="true"/>
存在这样的使用方法:
Customercustomer=newCustomer("Jack");
//session.save(customer);
Orderorder1=newOrder("Jack_Order001",customer);
Orderorder2=newOrder("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。这样可以提高持久化类的透明性。
在定义集合属性时,通常把他初始化为集合实现类的一个实例,privateSetorders=newHashSet();这样可以提高程序的健壮性,避免因为null而抛出异常。
映射方法:
<setname="orders"cascade="save-update">
<keycolumn="CUSTOMER_ID"/>
<one-to-manyclass="myapck.Order"/>
</set>
当建立order对象和customer对象的双向关联关系时,需要在程序中同时修改两个对象的属性:
-建立Order到Customer对象的多对一关系
order.setCustomer(customer)
hibernate探测到这个变化后会将order对应的CUSTOMER_ID的值改为customer的id,会执行SQL:
updateORDERSsetORDER_NUMBER="Jack_Order001",CUSTOMER_ID=2whereID=2;
-建立Customer对象到Order对象的一对多关联关系:
customer.getOrders().addOrder(order);
hibernate探测到这个变化后也会将order对应的CUSTOMER_ID的值改为customer的id,会执行SQL:
updateORDERSsetCUSTOMER_ID=2whereID=2;
这样会重复执行多余的SQL,影响性能。解决这一问题的方法是把<set>元素的inverse属性设置为true,默认为false;
<setname="orders"cascade="save-update"inverse="true">
<keycolumn="CUSTOMER_ID"/>
<one-to-manyclass="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="arbitrarySQLexpression"
node="element-name|@attribute-name|element/@attribute|."
embed-xml="true|false"
index="index_name"
unique_key="unique_key_id"
foreign-key="foreign_key_name"
/>
1
name:属性名。
2
column(可选):外间字段名。它也可以通过嵌套的<column>元素指定。
3
class(可选-默认是通过反射得到属性类型):关联的类的名字。
4
cascade(级联)(可选):指明哪些操作会从父对象级联到关联的对象。
5
fetch(可选-默认为select):在外连接抓取(outer-joinfetching)和序列选择抓取(sequentialselectfetching)两者中选择其一。
6
update,insert(可选-默认为true)指定对应的字段是否包含在用于UPDATE和/或INSERT的SQL语句中。如果二者都是false,则这是一个纯粹的“外源性(derived)”关联,它的值是通过映射到同一个(或多个)字段的某些其他属性得到或者通过trigger(触发器)、或其他程序生成。
6
property-ref:(可选)指定关联类的一个属性,这个属性将会和本外键相对应。如果没有指定,会使用对方关联类的主键。
7
access(可选-默认是property):Hibernate用来访问属性的策略。
8
unique(可选):使用DDL为外键字段生成一个唯一约束。此外,这也可以用作property-ref的目标属性。这使关联同时具有一对一的效果。
9
not-null(可选):使用DDL为外键字段生成一个非空约束。
10
optimistic-lock(可选-默认为true):指定这个属性在做更新时是否需要获得乐观锁定(optimisticlock)。换句话说,它决定这个属性发生脏数据时版本(version)的值是否增长。
11
lazy(可选-默认为proxy):默认情况下,单点关联是经过代理的。lazy="no-proxy"指定此属性应该在实例变量第一次被访问时应该延迟抓取(fetchelazily)(需要运行时字节码的增强)。lazy="false"指定此关联总是被预先抓取。
12
not-found(可选-默认为exception):指定外键引用的数据不存在时如何处理:ignore会将行数据不存在视为一个空(null)关联。
13
entity-name(可选):被关联的类的实体名。
14
formula(可选):SQL表达式,用于定义computed(计算出的)外键值。
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节“传播性持久化(transitivepersistence)”.注意,单值关联(many-to-one和one-to-one关联)不支持删除孤儿(orphandelete,删除不再被引用的值).
一个典型的简单many-to-one定义例子:
<many-to-onename="product"class="Product"column="PRODUCT_ID"/>
property-ref属性只应该用来对付遗留下来的数据库系统,可能有外键指向对方关联表的是个非主键字段(但是应该是一个惟一关键字)的情况下。这是一种十分丑陋的关系模型。比如说,假设Product类有一个惟一的序列号,它并不是主键。(unique属性控制Hibernate通过SchemaExport工具进行的DDL生成。)
<propertyname="serialNumber"unique="true"type="string"column="SERIAL_NUMBER"/>
那么关于OrderItem的映射可能是:
<many-to-onename="product"property-ref="serialNumber"column="PRODUCT_SERIAL_NUMBER"/>
当然,我们决不鼓励这种用法。
如果被引用的唯一主键由关联实体的多个属性组成,你应该在名称为<properties>的元素里面映射所有关联的属性。
假若被引用的唯一主键是组件的属性,你可以指定属性路径:
<many-to-onename="owner"property-ref="identity.ssn"column="OWNER_SSN"/>
------------------------------------------------------------------------------------------------------------
3、级联删除
如果在删除customer的时候,将其关联的orders也删除只需要设置cascade=delete
<setname="orders"casecade="delete"inverse="true">
<keycolumn="CUSTOMER_ID"/>
<one-to-manyclass="myapck.Order"/>
</set>
4、父子关系
解除customer与其中一个order之间的关系:
tx=session.beginTransaction();
Customercustomer=(Customer>session.load(Customer.class,newLong(2));
Orderorder=(Order)customer.getOrders().iterator().next();
customer.getOrders().remove(order);
order.setCustover(null);
tx.commit();
如果cascade取默认值“none”,当order.setCustomer(null)时,会执行:
updateORDERSsetCUSTOMER_ID=nullwhereID=2;
如果希望Hiernate自动删除不再和customer对象关联的order对象,可以把cascade属性设置为"all-delete-orphan"
<setname="orders"cascade="all-delete-orphan"inverse="true">
<keycolumn="CUSTOMER_ID"/>
<one-to-manyclass="myapck.Order"/>
</set>
此时如果执行order.setCustomer(null)会执行一下SQL:
deletefromORDERSwhereCUSTOMER_ID=2andID=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个)父类和(多个子类),其映射文件如下:
<?xmlversion="1.0"?>
<!DOCTYPEhibernate-mapping
PUBLIC"-//Hibernate/HibernateMappingDTD2.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">
<hibernate-mapping>
<classname="mypack.Category"table="CATEGORIES">
<idname="id"type="long"column="ID">
<generatorclass="increment"/>
</id>
<propertyname="name"type="string">
<columnname="NAME"length="15"/>
</property>
<set
name="childCategories"
cascade="save-update"
inverse="true"
>
<keycolumn="CATEGORY_ID"/>
<one-to-manyclass="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"
>
<keycolumn="CATEGORY_ID"/>
<one-to-manyclass="mypack.Category"/>
</set>
原理:
<one-to-many>表示单个该类对象对应多个子类别;[以主键去匹配外键]
<one-to-many>元素表示以该记录对应的表的主键(ID)去匹配"mypack.Category"对应的表中的"CATEGORY_ID"字段;