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

本文深入探讨了Hibernate中关联关系的多种类型,包括单向和双向关联的特点与应用场景,级联保存、更新和删除的配置方法,以及一对多双向自身关联的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

关联关系分:单向关联(一对多、多对一)和双向关联(一对多双向)
在关系数据库中,只存在外键参照关系,而且总是由“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"
        
/>
1

name: 属性名。

2

column (可选): 外间字段名。它也可以通过嵌套的 <column>元素指定。

3

class (可选 - 默认是通过反射得到属性类型): 关联的类的名字。

4

cascade(级联) (可选): 指明哪些操作会从父对象级联到关联的对象。

5

fetch (可选 - 默认为 select): 在外连接抓取(outer-join fetching)和序列选择抓取(sequential select fetching)两者中选择其一。

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): 指定这个属性在做更新时是否需要获得乐观锁定(optimistic lock)。 换句话说,它决定这个属性发生脏数据时版本(version)的值是否增长。

11

lazy (可选 - 默认为 proxy): 默认情况下,单点关联是经过代理的。lazy="no-proxy"指定此属性应该在实例变量第一次被访问时应该延迟抓取(fetche lazily)(需要运行时字节码的增强)。 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-orphanall,并且可以用逗号分隔符 来组合这些操作,例如,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个)父类和(多个子类),其映射文件如下:

<? xml version="1.0" ?>
<! 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"字段;

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值