2012-01-12 76 views
10

我們使用Hibernate作爲持久層並具有複雜的對象模型。在不暴露實際數據模型的情況下,我想使用下面的簡單示例來解釋問題。使用休眠保存實體與引用的依賴實體

class Person { 
    private Integer id; //PK 
    private String name; 
    private Account account; 
    // other data, setters, getters 
} 


class Account { 
    private Integer id; //PK 
    // other data, setters, getters 
} 

數據庫映射使用HBM如下定義:

<class name="Person" table="PERSON"> 
    <id name="id" column="ID"> 
     <generator class="native"/> 
    </id> 
    <version name="version" type="java.lang.Long"/> 
    <property name="name" type="java.lang.String" length="50" column="NAME"/> 
    <many-to-one name="account" column="ACCOUNT_ID" 
       class="com.mycompany.model.Account"/> 

</class> 

我要救鏈接到現有AccountPerson新填充的實例。該調用是由Web客戶端發起的,因此在我的層中,我獲得了Person的實例,該實例引用了僅保存其ID的Account實例。

如果我嘗試調用saveOrUpdate(person)以下異常被拋出:

org.hibernate.TransientObjectException: 
object references an unsaved transient instance - save the transient instance before flushing: 
com.mycompany.model.Account 

爲了避免這一點,我必須找到ID的Account持久化對象,然後調用person.setAccount(persistedAccount)。在這種情況下,一切正常。

但在現實生活中,我處理數十個相互引用的實體。我不想爲每個參考書寫特殊的代碼。

我想知道是否有這種問題的某種通用解決方案。

回答

7

使用cascade="all"要堅持一個實體,你只需要有它的直接依賴的引用。這些其他實體引用其他實體並不重要。

做到這一點,最好的辦法是讓一個代理引用的實體,甚至沒有進入數據庫,使用session.load(Account.class, accountId)

你在做什麼是應該做的正確的事情:得到永久賬號的參考,並將此引用到新創建的帳戶。

+0

謝謝你,@JB Nizet。這實際上是我的預期......我會在這裏發佈我的通用解決方案的描述,並很樂意知道您的意見。 – AlexR 2012-01-12 17:50:23

1

上的* *至 - 映射

+0

在問題中沒有*對多映射... – 2012-01-12 17:31:35

+0

@Bozho,我加了'cascade'。其實我只是忘了提及我已經玩過了。現在,當我嘗試保存具有合法ID的臨時帳戶的人時,我得到如下異常:org.hibernate.AssertionFailure:com.mycompany.model.Person條目中的空id(在發生異常後不刷新會話)' – AlexR 2012-01-12 17:45:08

+0

@JB Nizet,你寫。它是'多對一' – AlexR 2012-01-12 17:46:02

0

有你在many-to-one元素試過cascade="save-update"?休眠默認爲cascade="none" ...

+0

我試過了@Nacho。它無法正常工作。它會拋出其他異常(請參閱我對Bozho的回答的評論) – AlexR 2012-01-12 17:48:33

0

謝謝你的幫助。我已經實現了我自己的通用解決方案,但想知道是否存在其他解決方案。

我想與大家分享這個想法。我呼叫除ID之外不包含任何內容的參考實體(或可用於唯一標識實體的其他字段)placeholder

因此,我創建了註解@Placeholder並將其放在所有引用的字段上。在我們的例子中,它是Person類的帳戶字段。 我們已經有了名爲GenericDao的類,它包裝了Hibernate API並且有方法save()。我又添加了另一種方法saveWithPlacehodlers(),它執行以下操作。它通過反射發現給定對象的類,找到標註爲@Placeholder的所有字段,找到DB中的對象並調用主實體中適當的設置器以用持久實體替換引用的佔位符。

註釋@Placeholder允許定義字段將被用於識別實體。默認值是id

您認爲什麼人,關於這個解決方案的人?

+0

這些「自動」解決方案必須小心處理,如果我理解您的描述正確,您將解決一次處理一個對象的Save方法中的依賴關係。你在一個工作單元中保存了很多對象,你正在逐個加載所有的依賴對象,這是非常低效的。通常,數據庫操作是由一個存儲庫或一個服務類來完成的,兩者都應該有一個好的他們可以一次加載大量實體​​(例如使用in子句),也可以驗證是否已經存在依賴實體。 – oddparity 2013-08-24 18:58:25

2

Hibernate允許您僅使用帶ID的引用類,您不需要執行session.load()。

唯一重要的是,如果您的參考對象具有VERSION,則必須設置版本。 在你的情況下,你必須指定帳戶對象的版本。

+0

這是在Hibernate文檔還是其他可用文檔中描述的? – sbrattla 2016-06-16 20:07:28

+0

愛你man!我把這個版本添加到我的課程中,我不能通過設置ID來保存它,浪費了很多時間 – 2016-12-18 13:07:52

0

還有一個問題的解決方案 - 使用您想要引用的實體的默認構造函數,並設置id和版本(如果它是版本化的)。我們有以下DAO方法:

public <S extends T> S materialize(EId<S> entityId, Class<S> entityClass) { 
     Constructor<S> c = entityClass.getDeclaredConstructor(); 
     c.setAccessible(true); 

     S instance = c.newInstance(); 
     Fields.set(instance, "id", entityId.getId()); 
     Fields.set(instance, "version", entityId.getVersion()); 
     return instance; // Try catch omitted for brevity. 
} 

我們可以用這樣的方法,因爲我們不使用延遲加載,而是有其是在GUI中使用實體的「若干意見」。這允許我們擺脫Hibernate用來填充所有渴望關係的所有聯接。視圖總是有實體的id和版本。因此,我們可以通過創建一個在Hibernate中看起來不是瞬態的對象來填充參考。

我嘗試了這種方法和session.load()。他們都很好。我發現我的方法有一些優勢,因爲Hibernate不會在其代碼中的代理泄漏。如果沒有正確使用,我只會得到NPE而不是'沒有會話綁定到線程'異常。