原文:JPA implementation patterns: Removing entities
作者:Vincent Partington
出處:http://blog.xebia.com/2009/04/09/jpa-implementation-patterns-removing-entities/
過(guò)去幾周以來(lái)我一直在談?wù)撟约涸诰帉?xiě)JPA應用時(shí)發(fā)現的實(shí)施模式,上兩篇博客分別涉及了保存實(shí)體和檢索實(shí)體方面的內容,不過(guò)在真正完全實(shí)現實(shí)體的時(shí)候,我猜你是希望也能夠對它們進(jìn)行刪除操作的,所以,刪除就是本篇博客的主題。
就像檢索實(shí)體一樣,刪除一個(gè)實(shí)體是很簡(jiǎn)單的,實(shí)際上,所有需要做的就是把實(shí)體傳給EntityManager.remove方法(當然實(shí)際上是調用了DAO的一個(gè)刪除方法,該方法轉而調用EntityManager.remove),以便在事務(wù)提交的時(shí)候從數據庫中刪除該實(shí)體。通常情況下,這一切就是這么回事,不過(guò),事情會(huì )因為你正在使用關(guān)聯(lián)(無(wú)論它們是雙向的與否)而變得更有意思起來(lái)。
刪除作為關(guān)聯(lián)的組成部分的實(shí)體
考慮一下之前我們討論過(guò)的Order和OrderLine類(lèi)這一例子,比方說(shuō),我們要從Order中刪除OrderLine,我們以這種簡(jiǎn)單的方式來(lái)完成這件事:
orderLineDao.remove(lineToDelete);
這一代碼是有問(wèn)題的,當你告訴實(shí)體管理器刪除該實(shí)體的時(shí)候,實(shí)體管理器并不會(huì )自動(dòng)地從指向該實(shí)體的所有關(guān)聯(lián)中刪除它,就像JPA不會(huì )自動(dòng)地管理雙向關(guān)聯(lián)一樣,就例子中的情況而言,關(guān)聯(lián)指的應該是OrderLine.order屬性指向的Order對象中的orderLines集合。假如我要把這一描述寫(xiě)成一個(gè)測試失敗的JUnit用例的話(huà),那它應該是這樣的:
OrderLine orderLineToRemove = orderLineDao.findById(someOrderLineId);
Order parentOrder = orderLineToRemove.getOrder();
int sizeBeforeRemoval = parentOrder.getOrderLines().size();
orderLineDao.remove(orderLineToRemove);
assertEquals(sizeBeforeRemoval - 1, parentOrder.getOrderLines().size());
影響
這一測試用例的失敗有兩處不易察覺(jué)也因此是比較惡劣的影響:
在刪除OrderLine之后,任何使用Order對象的代碼相反仍然會(huì )看到被刪除的OrderLine,只有在提交該事務(wù),然后開(kāi)始一個(gè)新的事務(wù)并在新事務(wù)中重新加載Order之后,被刪除的OrderLine才不會(huì )在Order.orderLines集中出現。在簡(jiǎn)單的情況下我們不會(huì )遇到這一問(wèn)題,但在事情越來(lái)越復雜的時(shí)候,我們可能會(huì )被這些“僵尸”OrderLines的出現嚇一跳。
當持久操作從Order對象級聯(lián)到Order.orderLines關(guān)聯(lián)并且其包含的Order對象沒(méi)有在同一事務(wù)中被刪除時(shí),我們將會(huì )收到諸如“已刪除的實(shí)體被傳給了持久方法”一類(lèi)的錯誤。不同于我們在之前的博客中談到的“游離實(shí)體被傳給了持久方法”這一錯誤,當前的這一錯誤是由Order對象擁有到已被刪除的OrderLine對象的引用這一事實(shí)導致的,JPA提供程序在把持久性上下文中的實(shí)體刷新到數據庫中時(shí)發(fā)現了該引用,造成其企圖持久已經(jīng)被刪除的實(shí)體,從而導致了錯誤的出現。
簡(jiǎn)單的解決方案
為了解決這一問(wèn)題,我們還必須從Order.orderlines集中刪除OrderLine,這聽(tīng)起來(lái)非常熟悉……實(shí)際上,在管理雙向關(guān)聯(lián)時(shí),我們也需要確保關(guān)聯(lián)的兩端保持一致的狀態(tài),這意味著(zhù)我們可以重用在這方面用過(guò)的模式,通過(guò)把對orderLineToRemove.setOrder(null)的調用加入到測試中來(lái)使其成功運行:
OrderLine orderLineToRemove = orderLineDao.findById(someOrderLineId);
Order parentOrder = orderLineToRemove.getOrder();
int sizeBeforeRemoval = parentOrder.getOrderLines().size();
orderLineToRemove.setOrder(null);
orderLineDao.remove(orderLineToRemove);
assertEquals(sizeBeforeRemoval - 1, parentOrder.getOrderLines().size());
模式
不過(guò)類(lèi)似這樣的做法會(huì )使代碼變得脆弱,因為其依賴(lài)于領(lǐng)域對象的用戶(hù)調用正確的方法,DAO應該承擔這一工作。一個(gè)非常棒的解決這一問(wèn)題的方法是像這樣子來(lái)使用@PreRemove這一實(shí)體生命周期的鉤子:
@Entity
public class OrderLine {
[...]
@PreRemove
public void preRemove() {
setOrder(null);
}
}
現在我們只要調用OrderLineDao.remove()來(lái)除去不想要的OrderLine對象就可以了。
本篇博客最初是建議引入由DAO來(lái)調用的帶有preRemove方法的HasPreRemove接口,不過(guò)Sakuraba建議說(shuō),@PreRemove注解正好是我們這里要用到的東西,再次感謝你,Sakuraba!
刪除孤兒
但是你會(huì )問(wèn),如果我們只是要從Order.orderLines集中刪除OrderLine的話(huà),那事情會(huì )怎么樣呢?
Order parentOrder = orderLineToRemove.getOrder();
parentOrder.removeOrderLine(orderLineToRemove);
OrderLine確實(shí)已是從Order.orderlines集中刪除,并且不僅僅只是在該事務(wù)中,如果我們在新的事務(wù)中再次檢索該Order對象的話(huà),被刪除的OrderLine也不會(huì )出現。但是如果我們查看一下數據庫,就會(huì )看到該OrderLine還在,它只是把OrderLine.order字段設置成null罷了,我們在這里看到的是一個(gè)“已成孤兒的”集合元素,解決這一問(wèn)題有兩種方法:
如我們之前討論的那樣,顯式地刪除OrderLine對象。
如果正在使用Hibernate作為JPA提供程序的話(huà),可以讓Hibernate自動(dòng)地刪除這些孤兒,對你希望Hinernate這樣做的@OneToMany注解來(lái)說(shuō),在其下面添加值為org.hibernate.annotations.CascadeType.DELETE_ORPHAN的org.hibernate.annotations.Cascade注解,可參閱Hinernate文檔中的例子。
雖然第二種解決方案是供應商特定的,但確實(shí)有它的好處在,那就是代碼不需要每次從集合中刪除實(shí)體的時(shí)候都要調用DAO的刪除方法,不過(guò)為了明確地表明你是在使用供應商特定的擴展,你應該使用完整的包名稱(chēng)(既然Java Persistence with Hibernate一書(shū)也這樣建議)來(lái)引用這些注解:
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
@org.hibernate.annotations.Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private Set<OrderLine> orderLines = new HashSet<OrderLine>();
需要注意的是,CascadeType.All并未包含DELETE_ORPHAN這一級聯(lián)類(lèi)型,這就是為什在這里的例子中要顯式地設定它。
因此,我們可以得出結論,刪除實(shí)體是件簡(jiǎn)單的工作,除非你正在處理關(guān)聯(lián),在這種情況下需要特別小心,要確保從所有指向實(shí)體的對象中刪除該實(shí)體,并且同時(shí)要從數據庫中刪除它。我們下一篇博客中再見(jiàn)!在此期間,請在下面的評論部分中留下任何想說(shuō)的話(huà)。聯(lián)系客服