2012-06-13 18 views
10

Mam jedną BaseEntity, która abstrakty id i wersja właściwość. ta klasa implementuje również kod skrótu i ​​równe w oparciu o własność PK (id).Hibernate równa się i proxy

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; 
} 


} 

teraz dwa Jednostka A i B rozciąga BaseEntity jak poniżej

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; 

ścisłej sesja załadować z leniwy b i próbują a1.getB(). Equals (b1) daje fałszywe ale jeśli porównać z a1.getB(). getId(). equals (b1.getId()) wtedy daje prawdziwe dziwne !! Myślę, że to ze względu na obiekt proxy proxy Java, tak czy inaczej, aby rozwiązać ten problem?

Odpowiedz

15

Aby móc leniwie załadować skojarzenie a.b, Hibernate ustawia pole b na a na serwer proxy. Proxy jest instancją klasy, która rozszerza B, ale nie jest B. Więc twoja metoda equals() zawsze zawiedzie przy porównywaniu instancji B bez pośrednika z instancją proxy B, ponieważ porównuje klasy obu obiektów:

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

W przypadku podmiotów hibernacji, należy wymienić to z

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

należy również pamiętać, że

  • Hibernate nie zaleca wdrożenie equals() i hashCode() za pomocą identyfikatora, ale raczej za pomocą naturalnego identyfikatora. Implementacja go za pomocą identyfikatorów może powodować problemy, ponieważ jednostki nie mają identyfikatora, dopóki nie zostaną zapisane, a identyfikator zostanie wygenerowany.
  • Podczas korzystania z dziedziczenia jednostek problem jest jeszcze gorszy. Załóżmy, że B jest superklasą dwóch podjednostek B1 i B2. Hiberante nie może wiedzieć, który typ (B1 lub B2) jest a.b przed załadowaniem. Tak więc a.b zostanie zainicjowane na proxy, które jest podklasą B, ale nie jest podklasą B1 lub B2. Dlatego metody powinny być zaimplementowane w B, ale nie mogą być nadpisywane w B1 i B2. Dwie instancje B powinny być traktowane jednakowo, jeśli są instancjami B i mają ten sam identyfikator.
+0

Dzięki, rozumiem. zgadzam się z tobą na problemy, które mogą wyniknąć ze względu na equals() i hashcode() oparte na Id. Myślę, że teraz pójdę z opcją instanceof, ponieważ wprowadzenie naturalnego identyfikatora na tym etapie w aplikacji byłoby trudne. –

-1

Jest to głównie efekt standardowego dziedziczenia Java.

a1.getB().equals(b1) używa Object.equals() (z wyjątkiem sytuacji, gdy nadpisałeś equals() w klasie), która zwraca tylko wartość true, jeśli a1.getB() i b1 są tym samym wystąpieniem. Nie wiem, co dokładnie zrobiłeś (formatowanie twojego kodu jest zepsute), ale wygląda na to, że ponownie załadowałeś a w innej sesji, więc dostajesz nowe wystąpienie dla a i a.getB(), i w konsekwencji Object.equals() zwraca wartość false .

a1.getB().getId().equals(b1.getId()) używa Long.equals(), która zwraca wartość true, jeśli wartości długie są takie same (nawet dla różnych instancji obiektu Long), a wartości te są oczywiście takie same.

+0

poprawione formatowanie kodu, jak za mojego kodu myślę, że powinien on powołać się na równe 'BaseEntity' zamiast' Object.equals() ', obecnie jest wywoływanie (' Object.equals) 'nie wiem dlaczego –

+0

Czy można dodać kod dla A.getB() (który typ?), BaseEntity.equals() i dla mapowania A (przynajmniej identyfikator i członek b)? Być może wtedy widzę więcej. – Johanna

+0

zaktualizowany kod dla ustawiacza getter. –

1

Można również zrobić to działa w ten sposób, przydatnych, jeśli nie wiem, Wich instancji jest B (może się zdarzyć, jeśli equals jest w nadrzędnej)

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

Proszę zobaczyć mój komentarz do odpowiedzi Ralpha. Łączysz również swoje klasy domenowe z zależnością Hibernate. –

8

używam Hibernate.getClass przez wiele lat i nigdy nie zauważyłem problem:

@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

Oprócz tego, że twoje klasy domenowe są obecnie prawie niezwiązane z Hibernate. Załóżmy, że chcesz udostępnić swoją domenę innym aplikacjom, które w ogóle nie używają hibernacji ... masz kłopoty. Nie zrozum mnie źle, to zadziała, ale pachnie bardzo źle. Również, jeśli członkowie hibernacji zdecydują się przenieść \ usuń proxy proxy, a uaktualnisz hibernację - domena nie będzie się już kompilować. –