2011-11-27 121 views
8

我有一個多對一關係的家長實體與兒童實體:如何避免與JPA級聯重複?

@Entity class Parent { 
    // ... 
    @ManyToOne((cascade = {CascadeType.ALL}) 
    private Child child; 
    // ... 
} 

兒童有一個獨特的領域:

@Entity class Child { 
    // ... 
    @Column(unique = true) 
    private String name; 
    // ... 
} 

當我需要一個新的孩子,我問ChildDAO冷杉ST:

Child child = childDao.findByName(name); 
if(child == null) { 
    child = new Child(name); 
} 

Parent parent = new Parent(); 
parent.setChild(child); 

的問題是,如果我不喜歡上面兩次(與爲兒童相同的名稱),只有在堅持到底的家長,我得到一個約束的例外。這看起來很正常,因爲最初數據庫中沒有具有指定名稱的子項。

問題是,我不知道什麼是避免這種情況的最好方法。

+2

你確定你的模型好嗎?孩子可以有很多父母,父母只能有一個孩子? –

+0

我的答案假設一對一的關係 - 上面的評論是絕對正確的,它是一個奇怪的名字,如果它是正確的。 –

+0

是的,模型是正確的。我將這些實體重新命名爲例子,看起來我可以做得更好。 – Cos64

回答

9

您正在用new Child()兩次創建Child的兩個非持久性實例,然後將這兩個實例放在兩個不同的父級中。持久化父對象時,兩個新的子實例中的每一個都將通過級聯持久化/插入,每個都有不同的@Id。名稱上的唯一約束條件隨即打破。如果你正在做CascadeType.ALL,那麼每次你做new Child()你可能會得到一個單獨的持久對象。

如果您確實希望將兩個Child實例視爲具有相同ID的單個持久對象,則需要分別持久化它以與持久性上下文/會話關聯。隨後調用childDao.findByName將刷新插入並返回剛剛創建的新子項,因此您不會執行new Child()兩次。

1

您正在設置一個子對象,如果在數據庫中存儲了指向一個不存在的子對象的父對象,則將其保存在數據庫中。

當您創建一個新對象時,它必須由entityManager以與使用DAO「查找」對象相同的方式進行管理,它必須從DB獲取註冊並將該對象置於entityManager上下文中。

嘗試先堅持或合併子對象。

+0

父級的子級字段設置了「CascadeType.ALL」。不需要明確的合併。 –

6

你得到這個,因爲你試圖持久存在一個對象(相同的ID)。你的級聯可能不會持續它是MERGE/UPDATE/REMOVE/DETACH。避免這種情況的最佳方法是正確設置級聯或手動管理級聯。基本上說,級聯依然是通常的罪魁禍首。

你可能想是這樣的:

//SomeService--I assume you create ID's on persist 
saveChild(Parent parent, Child child) 
{ 
    //Adding manually (using your cascade ALL) 
    if(child.getId() == null) //I don't exist persist 
    persistence.persist(child); 
    else 
    persistence.merge(child); //I'm already in the DB, don't recreate me 

    parent.setChild(child); 
    saveParent(parent); //using a similar "don't duplicate me" approach. 
} 

級聯可以非常令人沮喪,如果你讓框架管理它,因爲你會偶爾錯過更新,如果是緩存(特別是在多對一集合關係)。如果你沒有明確地保存父級並允許框架處理級聯。一般來說,我允許我的父母通過DELETE/PERSIST從我的孩子處獲得Cascade.ALL的關係和級聯。我是Eclipselink用戶,所以我對Hibernate/other的使用經驗有限,我無法對緩存進行說明。 *真的,想一想 - 如果你要保存一個孩子,你真的想保存所有與它相關的對象嗎?另外,如果你添加一個孩子,你不應該通知父母嗎? *

在你的情況下,我只需要在我的Dao/Service中有一個「saveParent」和「saveChild」方法,以確保緩存正確並且數據庫是正確的以避免頭痛。手動管理意味着您將擁有絕對的控制權,您不必依賴級聯來完成您的骯髒工作。在一個方向上,他們非常棒,當他們來到「上游」時,你將會有一些意想不到的行爲。

3

如果您創建兩個對象,並讓JPA自動保留它們,那麼您將在數據庫中獲得兩行。因此,您有兩種選擇:不要創建兩個對象,或不要讓JPA自動保留它們。

爲了避免創建兩個對象,你必須安排你的代碼,這樣如果兩個父母試圖創建具有相同名稱的兒童,他們將得到相同的實際實例。您可能需要一個WeakHashMap(作用域爲當前請求,可能由本地變量引用),按名稱鍵入,其中父母可以查找其新的子級名稱以查看子級是否已存在。如果沒有,他們可以創建該對象並將其放入地圖中。

爲了避免讓JPA自動保留對象,請刪除級聯並使用persist在創建後立即手動將對象添加到上下文中。

由於持久化上下文基本上是附加到數據庫的欺騙性WeakHashMap,所以這些方法在涉及到它時非常相似。