Wie sollten bei Verwendung von JPA und Hibernate Equals und Hashcode implementiert werden?

102

Wie sollten Gleichheits- und Hashcode der Modellklasse in Hibernate implementiert werden? Was sind die häufigsten Fallstricke? Ist die Standardimplementierung für die meisten Fälle gut genug? Ist es sinnvoll, Geschäftsschlüssel zu verwenden?

Es scheint mir, dass es ziemlich schwierig ist, es in jeder Situation richtig zum Laufen zu bringen, wenn faules Abrufen, ID-Generierung, Proxy usw. berücksichtigt werden.

egaga
quelle
Siehe auch stackoverflow.com/a/39827962/548473 (Implementierung von spring-data-jpa)
Grigory Kislin

Antworten:

73

Hibernate hat eine schöne und lange Beschreibung, wann / wie equals()/ hashCode()in der Dokumentation überschrieben werden muss

Der Kern davon ist, dass Sie sich nur darum kümmern müssen, ob Ihre Entität Teil einer Instanz ist Setoder ob Sie ihre Instanzen trennen / anhängen werden. Letzteres ist nicht so üblich. Ersteres wird normalerweise am besten gehandhabt über:

  1. Basing equals()/hashCode() auf einem Business - Schlüssel - zB eine einzigartige Kombination von Eigenschaften , die während des Objekts zu ändern gehen (oder zumindest Session) Lebensdauer nicht.
  2. Wenn die oben nicht möglich ist , Basis equals()/ hashCode()auf Primärschlüssel , wenn es Satz und Objektidentität / System.identityHashCode()sonst. Der wichtige Teil hierbei ist, dass Sie Ihr Set neu laden müssen, nachdem eine neue Entität hinzugefügt und beibehalten wurde. Andernfalls kann es zu seltsamem Verhalten kommen (was letztendlich zu Fehlern und / oder Datenbeschädigungen führt), da Ihre Entität möglicherweise einem Bucket zugewiesen wird, der nicht mit dem aktuellen übereinstimmt hashCode().
ChssPly76
quelle
1
Wenn Sie "reload" @ ChssPly76 sagen, meinen Sie damit, ein refresh()? Wie kommt Ihre Entität, die den SetVertrag befolgt , in den falschen Bereich (vorausgesetzt, Sie haben eine ausreichend gute Hashcode-Implementierung)?
Nicht-Sequitor
4
Aktualisieren Sie die Sammlung oder laden Sie die gesamte (Eigentümer-) Entität neu, ja. Was den falschen Bucket betrifft: a) Sie fügen eine neue Entität zum Festlegen hinzu, deren ID noch nicht festgelegt ist, sodass Sie identityHashCode verwenden, mit dem Ihre Entität in Bucket 1 platziert wird. b) Ihre Entität (innerhalb des Satzes) bleibt bestehen, sie hat jetzt eine ID und daher verwenden Sie hashCode () basierend auf dieser ID. Es ist anders als oben und hätte Ihre Entität in den Eimer Nr. 2 gestellt. Angenommen, Sie haben an anderer Stelle einen Verweis auf diese Entität, versuchen Set.contains(entity)Sie anzurufen , und Sie werden zurückkehren false. Gleiches gilt für get () / put () / etc ...
ChssPly76
Sinnvoll, aber nie verwendet. IdentityHashCode selbst, obwohl ich sehe, dass es in der Hibernate-Quelle wie in ihren ResultTransformers verwendet wird
kein Sequitor
1
Wenn Sie den Ruhezustand verwenden, können Sie auch auf dieses Problem stoßen , für das ich noch keine Lösung gefunden habe.
Giovanni Botta
@ ChssPly76 Aufgrund von Geschäftsregeln, die bestimmen, ob zwei Objekte gleich sind, muss ich meine Gleichheits- / Hashcode-Methoden auf Eigenschaften stützen, die sich innerhalb der Lebensdauer eines Objekts ändern können. Ist das wirklich eine große Sache? Wenn ja, wie komme ich darum herum?
Ubiquibacon
39

Ich denke nicht, dass die akzeptierte Antwort richtig ist.

So beantworten Sie die ursprüngliche Frage:

Ist die Standardimplementierung für die meisten Fälle gut genug?

Die Antwort lautet ja, in den meisten Fällen.

Sie müssen nur überschreiben equals()und hashcode()ob die Entität in einem Set(sehr häufigen) AND verwendet wird Entität verwendet wird, wird die Entität von Ruhezustandsitzungen getrennt und anschließend erneut angehängt (was eine ungewöhnliche Verwendung von Ruhezustand ist).

Die akzeptierte Antwort gibt an, dass die Methoden überschrieben werden müssen, wenn eine der beiden Bedingungen erfüllt ist.

Phil
quelle
Dies stimmt mit meiner Beobachtung überein, Zeit herauszufinden, warum .
Vlastimil Ovčáčík
"Sie müssen equals () und hashcode () nur überschreiben, wenn die Entität in einem Set verwendet wird" ist völlig ausreichend, wenn einige Felder ein Objekt identifizieren, und Sie möchten sich daher nicht auf Object.equals () verlassen, um es zu identifizieren Objekte.
Davidxxx
17

Die beste equals/ hashCodeImplementierung ist , wenn Sie einen verwenden einzigartigen Geschäftsschlüssel .

Der Geschäftsschlüssel sollte über alle Entitätsstatusübergänge hinweg konsistent sein (vorübergehend, verbunden, getrennt, entfernt). Aus diesem Grund können Sie sich nicht auf die ID für die Gleichheit verlassen.

Eine andere Möglichkeit besteht darin, zur Verwendung von UUID-Kennungen zu wechseln , die von der Anwendungslogik zugewiesen werden. Auf diese Weise können Sie die UUID für das equals/ verwendenhashCode da die ID zugewiesen wird, bevor die Entität geleert wird.

Sie können sogar die Entitätskennung für equalsund verwenden hashCode. Dazu müssen Sie jedoch immer denselben hashCodeWert zurückgeben, um sicherzustellen, dass der hashCode-Wert der Entität über alle Entitätsstatusübergänge hinweg konsistent ist. In diesem Beitrag finden Sie weitere Informationen zu diesem Thema .

Vlad Mihalcea
quelle
+1 für den UUID-Ansatz. Setzen Sie das in ein BaseEntityund denken Sie nie wieder über dieses Problem nach. Es braucht ein bisschen Platz auf der DB-Seite, aber diesen Preis zahlen Sie besser für den Komfort :)
Martin Frey
12

Wenn eine Entität durch verzögertes Laden geladen wird, handelt es sich nicht um eine Instanz des Basistyps, sondern um einen dynamisch generierten Subtyp, der von javassist generiert wird. Daher schlägt eine Überprüfung desselben Klassentyps fehl. Verwenden Sie daher nicht:

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

Verwenden Sie stattdessen:

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

Dies ist auch eine gute Vorgehensweise, wie unter Implementieren von Equals in Java Practices erläutert .

Aus dem gleichen Grund funktioniert der direkte Zugriff auf Felder möglicherweise nicht und gibt anstelle des zugrunde liegenden Werts null zurück. Verwenden Sie daher keinen Vergleich für die Eigenschaften, sondern die Getter, da diese möglicherweise zum Laden der zugrunde liegenden Werte ausgelöst werden.

stivlo
quelle
1
Dies funktioniert, wenn Sie Objekte konkreter Klassen vergleichen, die in meiner Situation nicht funktioniert haben. Ich habe Objekte von Superklassen verglichen. In diesem Fall hat dieser Code für mich funktioniert: obj1.getClass (). IsInstance (obj2)
Tad
6

Ja, es ist schwer. In meinem Projekt sind equals und hashCode beide auf die ID des Objekts angewiesen. Das Problem dieser Lösung ist, dass keines von beiden funktioniert, wenn das Objekt noch nicht beibehalten wurde, da die ID von der Datenbank generiert wird. In meinem Fall ist das tolerierbar, da in fast allen Fällen Objekte sofort bestehen bleiben. Davon abgesehen funktioniert es hervorragend und ist einfach zu implementieren.

Carlos
quelle
Ich denke, wir haben die Objektidentität für den Fall verwendet, dass die ID nicht generiert wurde
Kathy Van Stone
2
Das Problem hierbei ist, dass sich Ihr Hashcode ändert, wenn Sie das Objekt beibehalten. Dies kann große nachteilige Folgen haben, wenn das Objekt bereits Teil einer Hash-basierten Datenstruktur ist. Wenn Sie also die Objektidentität verwenden, sollten Sie obj id so lange verwenden, bis das Objekt vollständig freigegeben ist (oder das Objekt aus hashbasierten Strukturen entfernen, beibehalten und dann wieder hinzufügen). Persönlich denke ich, dass es am besten ist, keine ID zu verwenden und den Hash auf unveränderlichen Eigenschaften des Objekts zu basieren.
Kevin Day
1

In der Dokumentation zu Hibernate 5.2 heißt es, dass Sie hashCode möglicherweise nicht implementieren möchten und dass dies - je nach Ihrer Situation - überhaupt gleich ist.

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

Im Allgemeinen sind zwei aus derselben Sitzung geladene Objekte gleich, wenn sie in der Datenbank gleich sind (ohne hashCode und equals zu implementieren).

Es wird kompliziert, wenn Sie zwei oder mehr Sitzungen verwenden. In diesem Fall hängt die Gleichheit von zwei Objekten von Ihrer Implementierung der Equals-Methode ab.

Außerdem treten Probleme auf, wenn Ihre Equals-Methode IDs vergleicht, die nur generiert werden, wenn ein Objekt zum ersten Mal beibehalten wird. Sie sind möglicherweise noch nicht da, wenn gleich aufgerufen wird.

Nina
quelle
0

Hier gibt es einen sehr schönen Artikel: https://docs.jboss.org/hibernate/stable/core.old/reference/en/html/persistent-classes-equalshashcode.html

Zitieren einer wichtigen Zeile aus dem Artikel:

Wir empfehlen, equals () und hashCode () mit Business Key Equality zu implementieren. Business Key Equality bedeutet, dass die equals () -Methode nur die Eigenschaften vergleicht, die den Business Key bilden. Dieser Schlüssel würde unsere Instanz in der realen Welt identifizieren (ein natürlicher Kandidatenschlüssel):

In einfachen Worten

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 
}

}
Ravi Shekhar
quelle
0

Wenn Sie überschrieben haben equals, stellen Sie sicher, dass Sie die Verträge erfüllen: -

  • SYMMETRIE
  • REFLEKTIEREND
  • TRANSITIV
  • KONSISTENT
  • NICHT NULL

Und überschreiben hashCode, da sein Vertrag von der equalsImplementierung abhängt.

Joshua Bloch (Designer des Collection Frameworks) forderte nachdrücklich die Einhaltung dieser Regeln.

  • Punkt 9: HashCode immer überschreiben, wenn Sie gleich überschreiben

Es gibt schwerwiegende unbeabsichtigte Auswirkungen, wenn Sie diese Verträge nicht befolgen. Zum Beispiel List#contains(Object o)könnte ein falscher booleanWert zurückgegeben werden, da der Generalvertrag nicht erfüllt ist.

Awan Biru
quelle