W jaki sposób należy zaimplementować equals i hashcode podczas korzystania z JPA i Hibernate


103

W jaki sposób należy zaimplementować równości i kod skrótu klasy modelu w Hibernate? Jakie są typowe pułapki? Czy domyślna implementacja jest wystarczająca w większości przypadków? Czy ma sens używanie kluczy biznesowych?

Wydaje mi się, że dość trudno jest go dobrze uruchomić w każdej sytuacji, gdy bierze się pod uwagę leniwe pobieranie, generowanie identyfikatora, proxy itp.


Zobacz także stackoverflow.com/a/39827962/548473 (implementacja spring-data-jpa)
Grigory Kislin

Odpowiedzi:


74

Hibernate ma ładny i długi opis tego, kiedy / jak zastąpić equals()/ hashCode()w dokumentacji

Istotą tego jest to, że musisz się tylko martwić, jeśli twoja istota będzie częścią a Setlub jeśli zamierzasz odłączać / dołączać jej instancje. To drugie nie jest tak powszechne. Ten pierwszy jest zwykle najlepiej obsługiwany przez:

  1. Opierając equals()/ hashCode()na kluczu biznesu - np unikalna kombinacja atrybutów, które nie ulegnie zmianie w trakcie obiektu (lub przynajmniej sesję) żywotność.
  2. Jeśli powyższe jest niemożliwe, oprzyj equals()/ hashCode()na kluczu podstawowym JEŚLI jest ustawiony i tożsamość obiektu / w System.identityHashCode()przeciwnym razie Ważnym elementem jest to, że trzeba przeładować odbiornika po nowej jednostki został dodany do niej i trwało; w przeciwnym razie możesz skończyć z dziwnym zachowaniem (ostatecznie skutkującym błędami i / lub uszkodzeniem danych), ponieważ Twój podmiot może zostać przypisany do zasobnika, który nie pasuje do swojego obecnego hashCode().

1
Kiedy mówisz „przeładuj” @ ChssPly76, masz na myśli zrobienie refresh()? W jaki sposób Twój podmiot, który przestrzega Setumowy , trafia do niewłaściwego segmentu (zakładając, że masz wystarczająco dobrą implementację kodu skrótu).
non sequitor

4
Odśwież kolekcję lub ponownie załaduj całą jednostkę (właściciela), tak. Jeśli chodzi o zły zasobnik: a) dodajesz nową jednostkę do ustawienia, jej identyfikator nie jest jeszcze ustawiony, więc używasz identityHashCode, który umieszcza twoją jednostkę w zasobniku # 1. b) Twoja jednostka (w zestawie) jest utrwalona, ​​ma teraz identyfikator, a zatem używasz hashCode () na podstawie tego identyfikatora. To różni się od wyżej i byłoby umieścić swoją jednostkę w wiaderku # 2. Teraz, zakładając, że masz odniesienie do tej jednostki w innym miejscu, spróbuj zadzwonić, Set.contains(entity)a wrócisz false. To samo dotyczy get () / put () / etc ...
ChssPly76

Ma sens, ale nigdy nie użyłem
IdentityHashCode,

1
Korzystając z Hibernacji, możesz również napotkać ten problem , na który nadal nie znalazłem rozwiązania.
Giovanni Botta

@ ChssPly76 Ze względu na reguły biznesowe, które określają, czy dwa obiekty są równe, będę musiał oprzeć moje metody równości / kodu skrótu na właściwościach, które mogą ulec zmianie w czasie życia obiektu. Czy to naprawdę wielka sprawa? Jeśli tak, jak mam to obejść?
ubiquibacon,

39

Nie sądzę, aby przyjęta odpowiedź była dokładna.

Aby odpowiedzieć na pierwotne pytanie:

Czy domyślna implementacja jest wystarczająca w większości przypadków?

Odpowiedź brzmi: tak, w większości przypadków tak.

Musisz tylko nadpisać equals()i hashcode()jeśli jednostka będzie używana w Set(co jest bardzo powszechne) ORAZ jednostka zostanie odłączona, a następnie ponownie dołączona do sesji hibernacji (co jest rzadkim użyciem hibernacji).

Zaakceptowana odpowiedź wskazuje, że metody należy zastąpić, jeśli którykolwiek z warunków jest prawdziwy.


Jest to zgodne z moimi obserwacjami, czas dowiedzieć się, dlaczego .
Vlastimil Ovčáčík

„Musisz tylko nadpisać equals () i hashcode (), jeśli encja będzie używana w zestawie”, wystarczy, jeśli niektóre pola identyfikują obiekt, więc nie chcesz polegać na Object.equals () w celu identyfikacji obiekty.
davidxxx

17

Najlepsze equals/ hashCodewdrożenie jest wtedy, gdy używasz unikalnego klucza biznesowego .

Klucz biznesowy powinien być spójny we wszystkich przejściach stanów encji (przejściowe, dołączone, odłączone, usunięte), dlatego nie można polegać na identyfikatorze dla równości.

Inną opcją jest przejście na używanie identyfikatorów UUID , przypisywanych przez logikę aplikacji. W ten sposób możesz użyć identyfikatora UUID dla equals/, hashCodeponieważ identyfikator jest przypisywany przed opróżnieniem jednostki.

Możesz nawet użyć identyfikatora jednostki dla equalsi hashCode, ale wymaga to zawsze zwracania tej samej hashCodewartości, aby upewnić się, że wartość hashCode jednostki jest spójna we wszystkich przejściach stanu jednostki. Sprawdź ten post, aby uzyskać więcej informacji na ten temat .


+1 za uuid podejście. Wrzuć to do BaseEntitytematu i nigdy więcej nie myśl o tym problemie. Zajmuje trochę miejsca po stronie db, ale tę cenę lepiej zapłacić za wygodę :)
Martin Frey

12

Gdy jednostka jest ładowana przez ładowanie z opóźnieniem, nie jest instancją typu podstawowego, ale jest dynamicznie generowanym podtypem generowanym przez javassist, dlatego sprawdzenie tego samego typu klasy zakończy się niepowodzeniem, więc nie używaj:

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

zamiast tego użyj:

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

co jest również dobrą praktyką, jak wyjaśniono w artykule Implementing equals in Java Practices .

z tego samego powodu bezpośredni dostęp do pól może nie działać i zwracać null zamiast wartości bazowej, więc nie używaj porównania właściwości, ale używaj metod pobierających, ponieważ mogą one wyzwalać ładowanie podstawowych wartości.


1
To działa, jeśli porównujesz obiekty konkretnych klas, które nie działały w mojej sytuacji. Porównywałem obiekty superklas, w którym to przypadku ten kod działał dla mnie: obj1.getClass (). IsInstance (obj2)
Tad

6

Tak, to trudne. W moim projekcie equals i hashCode opierają się na identyfikatorze obiektu. Problem z tym rozwiązaniem polega na tym, że żadne z nich nie działa, jeśli obiekt nie został jeszcze utrwalony, ponieważ identyfikator jest generowany przez bazę danych. W moim przypadku jest to tolerowane, ponieważ w prawie wszystkich przypadkach obiekty są utrwalane od razu. Poza tym działa świetnie i jest łatwy do wdrożenia.


Myślę, że użyliśmy tożsamości obiektu w przypadku, gdy identyfikator nie został wygenerowany
Kathy Van Stone

2
Problem polega na tym, że jeśli utrzymasz obiekt, kod skrótu ulegnie zmianie. Może to mieć duże szkodliwe skutki, jeśli obiekt jest już częścią struktury danych opartej na skrótach. Tak więc, jeśli skończysz z użyciem tożsamości obiektu, lepiej kontynuuj używanie obj id, dopóki obiekt nie zostanie całkowicie zwolniony (lub usuń obiekt ze struktur opartych na skrótach, utrwal, a następnie dodaj go z powrotem). Osobiście uważam, że najlepiej byłoby nie używać id i oprzeć hash na niezmiennych właściwościach obiektu.
Kevin Day

1

W dokumentacji Hibernate 5.2 jest napisane, że możesz w ogóle nie chcieć implementować hashCode i equals - w zależności od sytuacji.

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

Ogólnie rzecz biorąc, dwa obiekty załadowane z tej samej sesji będą równe, jeśli są równe w bazie danych (bez implementacji hashCode i equals).

To się komplikuje, jeśli używasz dwóch lub więcej sesji. W takim przypadku równość dwóch obiektów zależy od implementacji metody równości.

Co więcej, będziesz miał kłopoty, jeśli twoja metoda równości porównuje identyfikatory, które są generowane tylko podczas utrwalania obiektu po raz pierwszy. Może ich jeszcze nie być, gdy zostanie wywołany równy.


0

Jest tu bardzo fajny artykuł: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Cytując ważny wers z artykułu:

Zalecamy implementację equals () i hashCode () przy użyciu równości klucza biznesowego. Równość klucza biznesowego oznacza, że ​​metoda equals () porównuje tylko właściwości tworzące klucz biznesowy, klucz, który identyfikowałby naszą instancję w świecie rzeczywistym (naturalny klucz kandydujący):

W prostych słowach

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 
}

}

0

Jeśli zdarzyło Ci się zastąpić equals, upewnij się, że wypełniasz jego kontrakty: -

  • SYMETRIA
  • ODBLASKOWY
  • PRZECHODNI
  • ZGODNY
  • NON NULL

I zastąp hashCode, ponieważ jego umowa polega na equalswdrożeniu.

Joshua Bloch (projektant frameworka Collection) stanowczo nalegał, aby te zasady były przestrzegane.

  • punkt 9: Zawsze nadpisuj hashCode, gdy zastępujesz równa się

Niestosowanie się do tych umów powoduje poważne niezamierzone skutki. Na przykład List#contains(Object o)może zwrócić niewłaściwą booleanwartość, ponieważ umowa generalna nie została spełniona.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.