精通hibernate学习笔记(3)[关联关系]

mr丶韩 2010-10-19

关联关系分:单向关联(一对多、多对一)和双向关联(一对多双向)

在关系数据库中,只存在外键参照关系,而且总是由“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"字段;

相关推荐