2012-06-13 91 views
10

我有一個BaseEntity抽象id和版本屬性。這個類還實現了基於PK(id)屬性的hashcode和equals。休眠等於和代理

BaseEntity{ 

    Long id; 
    Long version; 

public int hashCode() { 
    final int prime = 31; 
    int result = 1; 
    result = prime * result + ((id == null) ? 0 : id.hashCode()); 
    return result; 
} 

public boolean equals(Object obj) { 
    if (this == obj) 
     return true; 
    if (obj == null) 
     return false; 
    if (getClass() != obj.getClass()) 
     return false; 
    BaseEntity other = (BaseEntity) obj; 
    if (id == null) { 
     if (other.id != null) 
      return false; 
    } else if (!id.equals(other.id)) 
     return false; 
    return true; 
} 


} 

現在二人實體A和B擴展BaseEntity如下

A extends BaseEntity{ 
    `B b` 
    B getB(){return b;) 
    void setB(B b){this.b=b;} 
} 

B extends BaseEntity{ 
} 

object b1; 
object a1; 
a1.set(b1); 
session.save(a1) //cascade save; 

關閉會話 負載懶惰B和嘗試a1.getB()。等於(B1),一個提供虛假 ,但如果我與a1.getB()。getId()。equals(b1.getId())比較然後給出真正的奇怪! 我認爲這是因爲java協助代理對象,無論如何解決這個問題?

回答

15

爲了能夠延遲加載a.b關聯,Hibernate將a中的b字段設置爲代理。代理是擴展B的類的一個實例,但不是B.因此,比較非代理B實例和代理B實例時,equals()方法總是失敗,因爲它比較了兩個對象的類:

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

在Hibernate實體的情況下,你應該

if (!(obj instanceof B)) { 
    return false; 
} 

而且替換此,請注意

  • 休眠建議不要實施equals()hashCode()通過使用ID,而是通過使用自然標識符。使用ID實現它可能會導致問題,因爲在保存ID並生成ID之前,實體沒有ID
  • 使用實體繼承時,問題更加嚴重。假設B是兩個子實體B1和B2的超類。在加載之前,Hiberante無法知道哪種類型(B1或B2)是a.b。因此a.b將被初始化爲代理,該代理是B的子類,但不是B1或B2的子類。所以hashCode()equals()方法應該在B中實現,但不能在B1和B2中覆蓋。如果兩個B實例是B的實例並且具有相同的標識符,則應將它們視爲相等。
+0

謝謝,明白了。同意你對可能由於基於ID的equals()和hashcode()而引起的問題。我認爲現在我會選擇使用instanceof選項,因爲在這個階段在應用程序中引入自然編號會很困難。 –

-1

這主要是標準Java繼承的影響。

a1.getB().equals(b1)使用Object.equals()(除非在類中重寫了equals(),否則只有在a1.getB()和b1是相同的實例時才返回true。我不知道你剛剛做了什麼(代碼格式已損壞),但它看起來像是在另一個會話中再次加載了a,因此你得到一個新實例aa.getB(),因此Object.equals()返回false 。

a1.getB().getId().equals(b1.getId())使用Long.equals(),如果long值相同(即使對於Long對象的不同實例),則返回true,並且這些值顯然是相同的。

+0

糾正代碼格式化,按我的代碼,我認爲它應該調用等於上'BaseEntity'而不是'的Object.Equals()',目前正在調用'的Object.Equals( )'不知道爲什麼 –

+0

可以添加A.getB()(哪種類型?),BaseEntity.equals()和A(至少id和成員b)的映射的代碼嗎?也許那時我可以看到更多。 – Johanna

+0

更新了getter setter的代碼。 –

1

您還可以是這樣工作的,有用的,如果你不知道至極的實例是B(如果你的equals處於超可能發生)

if (HibernateProxyHelper.getClassWithoutInitializingProxy(this) != HibernateProxyHelper.getClassWithoutInitializingProxy(obj)) 
    return false 
+0

請參閱我對拉爾夫答案的評論。您還將您的域類與Hibernate依賴關聯起來。 –

8

我用Hibernate.getClass多年我從來沒有注意到一個問題:

@Override  
public boolean equals(final Object obj) { 
    if (this == obj) { 
     return true; 
    } 
    if (obj == null) { 
     return false; 
    } 
    if (Hibernate.getClass(this) != Hibernate.getClass(obj)) { 
     return false; 
    } 

    ... check for values 

    return true; 
} 
+3

除了你的域類現在幾乎沒有配合Hibernate。假設你想與其他一些不使用休眠的應用程序共享你的域名。現在你遇到了麻煩。不要誤解我的意思,它會起作用,但它聞起來非常糟糕。此外,如果休眠人員決定移動\刪除代理util並且升級了hibernate - 域名將不再編譯。 –