2009-10-28 45 views
78

應該如何模型類的equals和hashCode在Hibernate中實現實施?常見的缺陷是什麼?大多數情況下,默認實現是否足夠好?使用業務密鑰有什麼意義嗎?應該如何equals和hashCode使用JPA時和Hibernate

在我看來,這是相當困難的得到它的權利,在任何情況下,當延遲抓取,ID生成,代理等都是考慮的工作。

+0

另請參見http://stackoverflow.com/a/39827962/548473(spring-data-jpa的實現) – GKislin 2016-10-03 09:07:25

回答

55

具有休眠的時候/如何documentation

覆蓋equals()/hashCode()一個漫長且愉快描述它的要點是你唯一需要擔心它,如果你的實體將是一個Set,或者如果你的一部分將要分離/附加它的實例。後者並不常見。前者通常是最好的處理通過:

  1. 對業務關鍵立足equals()/hashCode() - 例如在對象(或至少會話)生命週期中不會更改的屬性的唯一組合。
  2. 如果以上是不可能的,則在主鍵上設置基礎equals()/hashCode()如果設置了,則以及其他對象標識/ System.identityHashCode()重要這裏的一部分是,你需要重新加載你的設置後新的實體已被添加到它並堅持;否則最終會出現奇怪的行爲(最終導致錯誤和/或數據損壞),因爲您的實體可能被分配到與當前的hashCode()不匹配的存儲桶。
+1

當你說「重新加載」@ChssPly76你的意思是做一個'refresh()'?你的實體如何遵守'Set'合同,最終會出現在錯誤的桶中(假設你有足夠好的哈希碼實現)。 – 2009-10-28 19:21:26

+4

刷新收藏或重新加載整個(所有者)實體,是的。至於錯誤的桶:a)你添加新的實體來設置,它的ID還沒有設置,所以你使用的是identityHashCode,它將你的實體放在bucket#1中。 b)你的實體(在set中)是持久的,它現在有一個id,因此你使用基於該id的hashCode()。它與上面的不同之處在於**會將您的實體放置在第2個桶中。現在,假設你在其他地方持有對此實體的引用,請嘗試調用'Set.contains(entity)'並返回'false'。 – ChssPly76 2009-10-28 19:33:57

+0

有道理但從未使用過identityHashCode自己雖然我看到它在Hibernate源代碼中使用,就像在他們的ResultTransformers中一樣 – 2009-10-29 05:37:28

4

是的,這很難。在我的項目中,equals和hashCode都依賴於對象的id。這個解決方案的問題是,如果對象還沒有被保存,它們都不會工作,因爲id是由數據庫生成的。在我的情況下,這是可以容忍的,因爲在幾乎所有情況下,對象都會立即被持久化。除此之外,它很好用,很容易實現。

+0

我認爲我們所做的就是在沒有生成id的情況下使用對象標識 – 2009-10-28 17:22:40

+2

問題這裏是,如果你堅持對象,你的哈希碼改變。如果對象已經是基於散列的數據結構的一部分,那麼這可能會產生很大的不利結果。因此,如果你確實使用了對象標識,那麼最好繼續使用obj id,直到對象完全釋放(或者從任何基於散列的結構中刪除對象,然後將其添加回去)。就個人而言,我認爲最好不要使用id,並將哈希基於對象的不可變屬性。 – 2009-10-29 03:55:08

29

我不認爲接受的答案是正確的。

要回答原來的問題:

是默認的實現大多數情況不夠好?

答案是肯定的,它是大多數情況下。

你只需要重寫equals()hashcode()如果實體將在Set使用(這是很常見)實體將被分離,並隨後重新附着,休眠會話(這是一個罕見的休眠使用)。

接受的答案表示如果條件爲真,則需要重寫方法。

+0

這與我的觀察一致,有時間找出[爲什麼](http://docs.jboss.org/hibernate/core/4.0/manual/en-US/html/persistent -classes.html#持久類-equalshashcode)。 – 2016-02-11 16:31:24

+0

「如果實體將在Set中使用,則只需要覆蓋equals()和hashcode()就足夠了,如果某些字段標識對象,並且您不想依賴Object.equals()識別對象。 – davidxxx 2017-11-11 21:23:58

11

當實體通過延遲加載加載時,它不是基類型的實例,而是由javassist生成的動態生成的子類型,因此對相同類類型的檢查將失敗,因此請勿使用:

if (getClass() != that.getClass()) return false; 

改用:

if (!(otherObject instanceof Unit)) return false; 

這也是一個很好的做法,因爲在Implementing equals in Java Practices解釋。

由於相同的原因,直接訪問字段可能無法正常工作並返回null,而不是基礎值,因此不要使用屬性比較,而是使用getter,因爲它們可能觸發加載基礎值。

+2

什麼解釋,謝謝先生訪問地球.. – 2015-02-25 08:18:56

+0

如果你正在比較具體類的對象,這在我的情況不起作用,這工作。我正在比較超類的對象,在這種情況下,此代碼適用於我:obj1.getClass()。isInstance(obj2) – Tad 2015-08-07 19:38:42

9

最好的equals/hashCode實施是當你使用unique business key

商業密鑰應該在所有entity state transitions(瞬態,附加,分離,刪除)中保持一致,這就是爲什麼你不能依靠id來實現平等。

另一種選擇是切換到使用由應用程序邏輯分配的UUID identifiers。這樣,您可以使用UUID作爲equals/hashCode,因爲在實體被刷新之前分配了id。

您甚至可以使用equalshashCode實體標識符,但需要你總是返回相同hashCode值,以便您確保實體的hashCode值是所有實體狀態轉變相一致。檢出this post for more on this topic

+0

+1用於uuid方法。把它放到'BaseEntity'中,不要再考慮這個問題。它需要在數據庫方面的一點空間,但你更好的價格支付的安慰:) – 2016-08-25 13:49:55

0

有很不錯的文章在這裏:https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

從文章中引用的重要防線:

我們建議使用業務鍵值 平等實現equals()和hashCode()。業務鍵值相等的意思是,equals()方法 僅僅比較形成業務鍵的屬性,一個關鍵是 將確定在現實世界中我們的實例(自然的候選 鍵):

簡單來說

public class Cat { 

... 
public boolean equals(Object other) { 
    //Basic test/class cast 
    return this.catId==other.catId; 
} 

public int hashCode() { 
    int result; 

    return 3*this.catId; //any primenumber 
} 

} 
2

如果你碰巧覆蓋equals,確保履行合同: -

  • 對稱性
  • 反光
  • 傳遞性
  • 非零

,並覆蓋hashCode,作爲其合同依靠equals實施。

Joshua Bloch(Collection框架的設計者)強烈要求遵循這些規則。

  • 項目9:始終重寫了hashCode當您覆寫等於

有嚴重的時候,你不遵循這些合同意想不到的效果。例如,List.contains(Object o)可能因爲未履行一般合同而返回錯誤的boolean價值。

1

在Hibernate 5.2的文檔中,它表示你可能不想實現hashCode和等於根據你的情況。

https://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode

通常,如果他們是在數據庫中等於(不實施的hashCode和等於)來自相同會話加載兩個對象將是相等的。

如果您使用兩個或多個會話,則會變得複雜。在這種情況下,兩個對象的相等取決於您的equals方法實現。

此外,如果您的equals方法比較僅在第一次保持對象時生成的ID,則會遇到麻煩。當等號被呼叫時,他們可能不在那裏。

相關問題