Hibernate 級聯關係說明 - 關於cascade和inverse的用法

 

hibernate中一對多關聯時會經常用到inverse和cascade屬性 ,

inverse 有兩個值 true ,false  ;如果設置爲true 則表示當前對象不負責講級聯對象的狀態變化同步到數據庫 ;設置false則相反,其默認值爲false;


cascade 有五個選項 分別是: all ,delete ,none,save-update,delete-orphan ;
        all : 所有情況下均進行關聯操作。
        none:所有情況下均不進行關聯操作。這是默認值
        save-update:在執行save/update/saveOrUpdate時進行關聯操作。
        delete:在執行delete時進行關聯操作。
        delete-orphan: 當save/update/saveOrUpdate時,相當於save-update ;當刪除操作時,相當於delete ;


all的意思是save-update + delete
all-delete-orphan 的意思是當對象圖中產生孤兒節點時,在數據庫中刪除該節點
all比較好理解,舉個例子說一下all-delete-orphan:
Category與Item是一對多的關係,也就是說Category類中有個Set類型的變量items.
舉個例子,現items中存兩個Item, item1,item2,如果定義關係爲all-delete-orphan
當items中刪除掉一個item(比如用remove()方法刪除item1),那麼被刪除的Item類實例
將變成孤兒節點,當執行category.update(),或session.flush()時
hibernate同步緩存和數據庫,會把數據庫中item1對應的記錄刪掉


測試Hibernate中的三個屬性:lazy,inverse,cascade

【測試環境】
一對多關係的兩張表:boy、girl(一個男孩可以多個女朋友)

 boy表結構
 Field   Type        
 ------  ----------- 
 name    varchar(50)  pk
 age     varchar(50) 

 girl表結構
 Field   Type        
 ------  ----------- 
 name    varchar(50)  pk
 bf      varchar(50)  fk 

【保存時:Inverse與cascade

 創建三個girl對象和一個boy對象,讓這是三個girl都是boy的女朋友

  ---------創建對象的代碼片段-----------
  Boy boy = new Boy("tom","23", null);

  Set girls = new HashSet();
  
  Girl g[] = new Girl[]{
                        new Girl("Alice1", boy),
                        new Girl("Alice2", boy),
                        new Girl("Alice3", boy)};
  girls.add(g[0]);
  girls.add(g[1]);
  girls.add(g[2]);
  
  boy.setGirls(girls);
 
 在Boy.hbm.xml中設置,然後對boy對象進行保存。

 1.Inverse = true,不指定cascade
   cascade的默認值爲none, 當對boy進行保存操作時,girl什麼都不做. 所以只保存了boy對象, 沒有保存girl對象

 2.Inverse = true,cascade=all
   boy與girl對象,包擴外鍵都成功保存。
   (生成3條SELECT語句和4條INSERT語句,一下簡稱SELECT 3, INSERT 4)

 3.Inverse = false,不指定cascade
   報錯。因爲boy爲主控方,負責維護關係,所以在插入boy對象後,會嘗試修改並不存在的girl對象。

 4.Inverse = false,cascade=all
   boy與girl對象,包擴外鍵都成功保存。
   (SELECT 4, INSERT 4, UPDATE 3)

   分析:除了4條INSERT語句之外,其他的6條語句是我們爲了圖方便付出的代價:3條SELECT語句用來判斷girl對象是否在數據表中已經存在,3條UPDATE語句是爲了維護外鍵關係

 高效率的做法:在Boy.hbm.xml中設置Inverse=true,在Girl.hbm.xml中設置Inverse=false, cascade=all,然後保存三個girl對象
 (SELECT 1, INSERT 4)

   高效率的代價就是保存的時候比較麻煩

【刪除時:Inverse與cascade

 希望通過刪除boy,也將3個girl對象刪除。程序中先查出boy對象,然後進行刪除
  -----------------------------------------
  Boy boy = (Boy) s.get(Boy.class, "tom");
  s.delete(boy);
  -----------------------------------------


 同樣在Boy.hbm.xml中進行設置

 1.Inverse = true
   可以猜到結果是出錯。原因:外鍵約束錯誤

 2.Inverse = false
   boy刪除,girl表中外鍵變爲null,沒有刪除記錄 ;  
(UPDATE 1, DELETE 1)

 3.Inverse = false, cascade = all
          全部刪除  ;在刪除有外鍵的從表時,先把從表外鍵置爲null,然後刪除主表記錄,最後根據從表主鍵刪除所有相關從表記錄

   (UPDATE 1, DELETE 4)

 4.Inverse = true, cascade = all
          全部刪除
   (DELETE 4)

Inverse是hibernate雙向關係中的基本概念,當然對於多數實體,我們並 不需要雙向關聯,更多的可能會選擇單向關聯,況且我們大多數人一般採用一對多關係,而一對多雙向關聯的另一端:多對一的inverse屬性是不存在,其實 它默認就是inverse=false.從而防止了在一對多端胡亂設置inverse也不至於出錯。但是inverse設置不當確實會帶來很大的性能影 響,這點是我們必須關注的。

這篇文章已經詳細分析了inverse設置不當帶來的影響:

http://www.hibernate.org/155.html

看了這篇文章,還是很有必要再寫下一些總結的:

1)inverse中提及的side其實是指一個類或者表的概念,雙向關聯其實是指雙方都可以取得對方的應用。

2)維護關係這個名詞還是稍顯模糊或者晦澀。我們一般說A類或者A表(這裏的表的是指多對多的連接表)有責任維護關係,其實這裏的意思是說,我在應 用在更新,創建,刪除(讀就不用說了,雙向引用正是爲了方便讀而出現)A類或者A表時,此時創建的SQL語句必須有責任保證關係的正確修改。

3)inverse=false的side(side其實是指inverse=false所位於的class元素)端有責任維護關係,而inverse=true端無須維護這些關係。

4)我們說inverse設立不當會導致性能低下,其實是說inverse設立不當,會產生多餘重複的SQL語句甚至致使JDBC exception的throw。這是我們在建立實體類關係時必須需要關注的地方。一般來說,inverse=true是推薦使用,雙向關聯中雙方都設置 inverse=false的話,必會導致雙方都重複更新同一個關係。但是如果雙方都設立inverse=true的話,雙方都不維護關係的更新,這也是 不行的,好在一對多中的一端:many-to-one默認是inverse=false,避免了這種錯誤的產生。但是對多對就沒有這個默認設置了,所以很 多人經常在多對多的兩端都使用inverse=true,結果導致連接表的數據根本沒有記錄,就是因爲他們雙分都沒有責任維護關係。所以說,雙向關聯中最 好的設置是一端爲inverse=true,一端爲inverse=false。一般inverse=false會放在多的一端,那麼有人提問了, many-to-many兩邊都是多的,inverse到底放在哪兒?其實hibernate建立多對多關係也是將他們分離成兩個一對多關係,中間連接一個連接表。所以通用存在一對多的關係,也可以這樣說:一對多是多對多的基本組成部分。

看下面的多對多的定義大家更會清楚”多對多“與“一對多”的關係:其中我們注意<many-to-many />標籤的特點就知道,它是定義了一個多對多關係,而不是<one-to-many/>。

<?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  package="org.hibernate.auction">
 <class name="TestA" table="TestA"
 dynamic-update="true" dynamic-insert="true" >

  <id name="id" column="id" type="int" unsaved-value="any" >
   <generator class="assigned">
   </generator>
  </id>

  <property name="name" type="java.lang.String"
   update="true" insert="true" column="name" />

  <set name="testBs" table="TestA_TestB" inverse="false" cascade="all">
   <key column="testA"/>
   <many-to-many column="testB" class="TestB" />
  </set>


 </class>
 <class name="TestB" table="TestB"
 dynamic-update="true" dynamic-insert="true" >

  <id name="id" column="id" type="int" unsaved-value="any" >
   <generator class="assigned">
   </generator>
  </id>

  <property name="name" type="java.lang.String" update="true"
  insert="true" column="name" />

  <set name="testAs" table="TestA_TestB" inverse="true" cascade="all">
   <key column="testB"/>
   <many-to-many column="testA" class="TestA" />
  </set>


 </class>
</hibernate-mapping>

在對多對中,因爲一端維護關係另一端不維護關係的原因,我們必須注意避免在應用中用不維護關係的類建立關係,因爲這樣建立的關係是不會在數據庫中存儲的。基於上面的映射文件代碼給出一個例子:

package org.hibernate.auction;
import java.util.*;

/**
 * @author Administrator
 *
 * To change the template for this generated type comment go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
public class TestA {
 int id;
 String name;
 Set testBs=new HashSet();
 public TestA(){
  
 }
 public TestA(int id){
  setId(id);
 }
 public int getId(){
  return id;
 }
 public void setId(int id){
  this.id=id;
 }
 public String getName(){
  return name;
 }
 public void setName(String name){
  this.name=name;
 }
 public Set getTestBs(){
  return testBs;
 }
 public void setTestBs(Set s){
  testBs=s;
 }
 public void addTestB(TestB tb){
  testBs.add(tb);
 }

 public static void main(String[] args) {
 }
}

public class TestB {

 
 int id;
 String name;
 Set testAs=new HashSet();
 public TestB(){
  
 }
 public TestB(int id){
  setId(id);
 }
 public int getId(){
  return id;
 }
 public void setId(int id){
  this.id=id;
 }
 public String getName(){
  return name;
 }
 public void setName(String name){
  this.name=name;
 }
 public Set getTestAs(){
  return testAs;
 }
 public void setTestAs(Set s){
  testAs=s;
 }
 public void addTestA(TestA ta){
  testAs.add(ta);
 }
 public static void main(String[] args) {
 }
}

測試代碼:

public void doTest() throws Exception{
  TestA a1=new TestA(1);
  TestA a2=new TestA(2);
  TestA a3=new TestA(3);
  TestB b1=new TestB(1);
  TestB b2=new TestB(2);
  TestB b3=new TestB(3);
  a1.addTestB(b1);
  a1.addTestB(b2);
  a1.addTestB(b3);
  b2.addTestA(a1);
  b2.addTestA(a2);
  
  Session s = factory.openSession();
  
  s = factory.openSession();
  
  
  Session session = factory.openSession();
  session.save(a1);
  session.flush();
  session.close();

 }

測試後連接表的數據爲:

testa              testb

1                  1

1                  2

1                  3

根據inverse規則,對這些代碼:b2.addTestA(a1);  b2.addTestA(a2); 建立的關係,數據庫並沒有存儲下來,因爲TestB沒有責任維護這些關係,所以產生的sql語句自然不會有針對Testa_testB表的操作了。假設應 用中真的需要這些方法,那麼我們可以修改TestB的方法,讓他們注意在維護端類中執行相應的操作以使得關係能夠在數據庫中保存下來,更改TestB如 下:

/*
 * Created on 2004-7-25
 *
 * To change the template for this generated file go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
package org.hibernate.auction;
import java.util.*;

/**
 * @author Administrator
 *
 * To change the template for this generated type comment go to
 * Window&gt;Preferences&gt;Java&gt;Code Generation&gt;Code and Comments
 */
public class TestB {

 
 int id;
 String name;
 Set testAs=new HashSet();
 public TestB(){
  
 }
 public TestB(int id){
  setId(id);
 }
 public int getId(){
  return id;
 }
 public void setId(int id){
  this.id=id;
 }
 public String getName(){
  return name;
 }
 public void setName(String name){
  this.name=name;
 }
 public Set getTestAs(){
  return testAs;
 }
 public void setTestAs(Set s){
  testAs=s;
 }
 public void addTestA(TestA ta){
  testAs.add(ta);
  ta.addTestB(this);
 }
 public static void main(String[] args) {
 }
}
那麼測試執行後連接表的數據爲:

testa          testb

1               2

1               3

1                1

2                 2

測試通過。


hibernate cascade備忘
當關聯雙方存在父子關係,就可以在 set 處設定 cascade 爲 all-delete-orphan

所謂父子關係,即指由父方控制子方的持久化聖明週期,子方對象必須和一個父方對象關聯。如果刪除父方對象,應該級聯刪除所有關聯的子方對象;如果一個子方對象不再和一個父方對象關聯,應該把這個子方對象刪除。

all-deleteorphan 的能力:

1. 當保存或更新父方對象時,級聯保存或更新所有關聯的子方對象,相當於 cascade 爲 save-update

2. 當刪除父方對象時,級聯刪除所有關聯的子方對象,相當於 cascade 爲 delete

3. 刪除不再和父方對象關聯的所有子方對象

解除父子關係的 java 語句例如:

customer.getOrders().remove(order);
order.setCustomer(null);

tx.commit();

如果 cascade 屬性取默認值 null,當解除父子關係時,會執行如下 sql:

update ORDER set CUSTOMER_ID=null where ID=2

inverse 設定的原則:

1. 在映射一對多雙向關聯關係時,應該設定 many 方的 inverse 爲 true,以提高性能

2. 在建立兩個對象的雙向關聯時,應同時修改關聯兩端的對象的相應屬性:

1)customer.getOrders().add(order);
2)order.setCustomer(customer);

如果不執行 1)而僅執行 2),由於 set 元素的 inverse 爲 true,因此 hibernate 不會按照 CUSTOMER 對象的狀態變化來同步數據庫。

inverse 解決性能問題的例子:

1. 建立 Order 到 Customer 的多對一關聯關係

order.setCustomer(customer);

相應執行的 SQL 爲:

update ORDERS set ORDER_NUMBER='Jack_Order001', CUSTOMER_ID=2 where ID=2;

2. 建立 Customer 到 Order 的一對多關係

customer ORDERS set CUSTOMER_ID=2 where ID=2;

相應 SQL 爲:

update ORDERS set CUSTOMER_ID=2 where ID=2;

顯然 1 和 2 的 SQL 功能重複了,反覆執行這樣的 SQL 語句會引起性能下降,因此:

inverse 設定爲 true 時,聲明 Customer 端的關聯只是 Order 端關聯的鏡像。當兩者狀態發生變化時,Hibernate 僅按照 Order 對象狀態變化來同步數據庫。即僅會執行以下 SQL:

update ORDERS set ORDER_NUMBER='Jack_Order001', CUSTOME_ID=2 where ID=2;

Customer.hbm.xml 片段如下:

<set
name="orders"
cascade="all-delete-orphan"
inverse="true"
>
<key column="CUSTOMER_ID" />
<one-to-many class="mypack.Order" />
</set>


引用:http://huhu0817.javaeye.com/blog/97831
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章