1. 程式人生 > >hibernate 級聯刪除策略(註解版、資料庫版)

hibernate 級聯刪除策略(註解版、資料庫版)

資料庫中的表一般都是相互關聯的,它們通過foreign key產生關係。
定義foreign key約束時可以指定三種引用行為:delete cascade、delete set null、delete no action,預設是delete on action。它們的含義是:
  • 1、delete cascade : 刪除主表的同時也刪除子表有關的記錄。這個行為適合主從表關係較為緊密的情況,比如選單和子選單。當主表的記錄不存在時,從表的資料已經沒有意義,存在也是多餘,所以當刪除主表時,從表相關記錄也一同刪除。
  • 2、delete set null : 刪除主表時將子表外來鍵設定為NULL。這個行為適合主從表關係不是相當密切的情況,比如角色和使用者,一個角色可以對應多個使用者(一個使用者也可以有多重角色),不能因為刪除了某個角色,而把屬於這個角色的使用者都刪除了,因為這些使用者可能還和其他表有很大關係。
  • 3、delete no action: 不做任何操作。這個情況下,如果主表的某個記錄已經被引用,刪除這條記錄會失敗,主要提醒使用者注意資料完整性。
以上3種情況在hibernate下如果實現呢?下面以多對一關聯舉例。
假設新聞與使用者存在多對一關聯
1、刪除使用者的同時也刪除他發表的新聞
News對映檔案:
<many-to-one name="editer" class="Account">
<column name="editer_id"></column>
</many-to-one>
Account對映檔案:
<set name="news" lazy="true" cascade="delete" inverse="true">
<!-- 主鍵對應的關聯表外來鍵 -->
<key column=" editer_id"></key>
<!-- 關聯表 -->
<one-to-many class="News"/>
</set>
2、刪除使用者時不刪除他發表的新聞
其實新聞表除了儲存使用者ID外,我建議還儲存使用者的名字,多了一點資料冗餘,但是在查詢新聞時不用外連線使用者表,而且在刪除新聞時,雖然外來鍵置NULL,但使用者名稱還在,我們還能知道這新聞是誰釋出的。
<set name="news" lazy="true" cascade="save-update" inverse="false">
<!-- 主鍵對應的關聯表外來鍵 -->
<key column=" editer_id"></key>
<!-- 關聯表 -->
<one-to-many class="News"/>
</set>
注意以上的inverser=false
3、刪除使用者時不執行額外操作
只要在<set>中設定 cascade="none"
注意
以上情況只是一對多關係,對於多於多等關係還是有些區別的,比如我通過中間表對映的多對多關係,在沒有設定cascade的情況下,刪除記錄時,也會主動刪除中間表中的記錄,如:
menu對映檔案
<set name="roles" lazy="true" table="MenuRole" >
<!-- 主鍵對應的中間表的外來鍵 -->
<key column="menu_id" />
<many-to-many class="Role" column="role_id"/>
</set>

刪除選單時,會主動從中間表menurole刪除記錄

很多人對持久層概念搞不清JPA、Hibernate、EJB3.0的關係,這裡做一下簡單的說明:JPA是一個持久層設計介面,EJB3.0和Hibernate是具體的實現類,EJB3.0和Hibernate的功能近似相等的(Hibernate沒有Session Bean,Spring MVC3的SessionAttribute跟Session Bean近似)。
理論是使用JPA介面可以無縫切換持久層實現,但是僅僅是理論上!!!




JPA是在Hibernate成熟並大行其道的時候才推出的,基本上是借鑑Hibernate的優點,做了一個統一的標準而已,JPA1.0沒有一對多的級聯刪除配置,也許JPA2.0裡才有吧(這裡沒做過調研)
@OneToMany(mappedBy = "commentTeam")
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
private Set<CommentTeamMember> commentTeamMembers;
這裡為了說明,只貼出一對多的關鍵程式碼,其它無關的註解已忽略掉,以免造成干擾。
這裡重點說明一下四個常用的註解配置的區別:
CascadeType.SAVE_UPDATE
CascadeType.ALL
CascadeType.DELETE
CascadeType.DELETE_ORPHAN




之所有之列出這四個,是因為我不想跟書本上把所有的概念都羅列出來。基本上開發時其中的3個都以及足夠用了,下面我結合程式碼演示一下他們之間的區別,以及使用的時候注意的地方。
CascadeType.SAVE_UPDATE:Hibernate專有的,JPA並不支援,作用是級聯儲存、級聯更新(注:JPA很噁心,要麼你配置
CascadeType.ALL,要麼你配CascadeType.SAVE+CasadeType.Merge。八卦一句:專家雖牛,多年不寫程式碼,定的標準讓編碼麻煩呀!)
CascadeType.ALL:級聯儲存、修改、刪除、同步,一般很少用,看看控制檯的一長串SQL就知道效能低下,你沒改的關聯表也給你發update語句,我從來沒用過這個屬性。
CascadeType.DELETE:當呼叫session.delete(A)的時候,級聯刪除關聯的物件。(注:先呼叫A.setB(null),再呼叫session.delete(A),這樣是級聯刪不掉B的。
CascadeType.DELETE_ORPHAN:一對多級聯刪除。




下面重點來說說這個CascadeType.DELETE_ORPHAN:
看過API、開發指南,級聯刪除就一個經典的
@OneToMany(mappedBy = "commentTeam")
@Cascade({CascadeType.SAVE_UPDATE,CascadeType.DELETE_ORPHAN})
private Set<CommentTeamMember> commentTeamMembers;




mappedBy不可少,對映A->B一對多的另一邊控制反轉(誰控誰的問題),新版的Hibernate3.4中配置更簡單,變一句了,更簡潔吧?
@OneToMany(mappedBy = "commentTeam",orphanRemoval=true) 
private Set<CommentTeamMember> commentTeamMembers;




以上兩種配置方式是等價的,下面是在實際開發中的使用了,有些時候程式碼方面不注意,會誤以為明明配置正確了,但為什麼不起作用呢? 下面例舉一下程式碼,請看Action程式碼(實際上我在Spring Controller裡,N年不寫DAO了,Service很少用,Manager一邊去!)
CommentTeam commentTeam=this.getHibernateTemplate.get(CommentTeam.class,id);
commentTeam.setCommentTeamMember(null);//想級聯刪除子表資料
this.getHibernateTemplate.saveOrUpdate(commentTeam);
這樣級聯刪除卻沒有發生?為什麼呢?
再來一個例子
CommentTeam commentTeam=this.getHibernateTemplate.get(CommentTeam.class,id);
Set<CommentTeamMember> commentTeamMembers=new HashSet<CommentTeamMember>();
commentTeam.setCommentTeamMember(commentTeamMembers);//想級聯刪除子表資料或增減替換物件
this.getHibernateTemplate.saveOrUpdate(commentTeam);
這個例子級聯刪除的效果也沒發生!即使commentTeamMembers理由有若干個物件。




成功執行級聯刪除的語法:
CommentTeam commentTeam=this.getHibernateTemplate.get(CommentTeam.class,id);
commentTeam.getCommentTeamMember().clear();//注意這裡引用的集合還是原理的集合,這裡沒有重新new過
commentTeam.getCommentTeamMember().add(new CommentTeamMember());//如果想替換為新的集合可以用addAll方法
this.getHibernateTemplate.saveOrUpdate(commentTeam);
分析一下原因:級聯刪除起作用的前提是關聯的集合物件不能重新指向新的引用,必須在原有的集合裡操作新增、刪除、清空元素,像上面的setXXX(null)的方法等是起步到級聯刪除作用的,大概是Hibernate自認自己原生的集合物件吧,自己New的放進行級聯刪除無效!