Es gab hier einige Diskussionen über JPA-Entitäten und welche hashCode()
/ equals()
Implementierung für JPA-Entitätsklassen verwendet werden sollte. Die meisten (wenn nicht alle) von ihnen hängen vom Ruhezustand ab, aber ich würde sie gerne neutral über die JPA-Implementierung diskutieren (ich verwende übrigens EclipseLink).
Alle möglichen Implementierungen haben ihre eigenen Vor- und Nachteile in Bezug auf:
hashCode()
/equals()
Vertrag Konformität (Unveränderlichkeit) fürList
/Set
Operationen- Ob identische Objekte (z. B. aus verschiedenen Sitzungen, dynamische Proxys aus träge geladenen Datenstrukturen) erkannt werden können
- Gibt an, ob sich Entitäten im getrennten (oder nicht persistenten) Zustand korrekt verhalten
Soweit ich sehen kann, gibt es drei Möglichkeiten :
- Überschreibe sie nicht; verlassen Sie sich auf
Object.equals()
undObject.hashCode()
hashCode()
Ichequals()
arbeite- kann keine identischen Objekte identifizieren, Probleme mit dynamischen Proxys
- Keine Probleme mit getrennten Entitäten
- Überschreiben Sie sie basierend auf dem Primärschlüssel
hashCode()
Ichequals()
bin kaputt- korrekte Identität (für alle verwalteten Entitäten)
- Probleme mit getrennten Einheiten
- Überschreiben Sie sie basierend auf der Geschäfts-ID (Nicht-Primärschlüsselfelder; was ist mit Fremdschlüsseln?)
hashCode()
Ichequals()
bin kaputt- korrekte Identität (für alle verwalteten Entitäten)
- Keine Probleme mit getrennten Entitäten
Meine Fragen sind:
- Habe ich eine Option und / oder einen Pro / Contra-Punkt verpasst?
- Welche Option haben Sie gewählt und warum?
UPDATE 1:
Mit " hashCode()
/ equals()
sind fehlerhaft" meine ich, dass aufeinanderfolgende hashCode()
Aufrufe möglicherweise unterschiedliche Werte zurückgeben, die (bei korrekter Implementierung) nicht im Sinne der Object
API-Dokumentation fehlerhaft sind, sondern Probleme beim Abrufen einer geänderten Entität aus einem Map
, verursachen. Set
oder andere Hash-basiert Collection
. Folglich funktionieren JPA-Implementierungen (zumindest EclipseLink) in einigen Fällen nicht richtig.
UPDATE 2:
Vielen Dank für Ihre Antworten - die meisten von ihnen haben eine bemerkenswerte Qualität.
Leider bin ich mir immer noch nicht sicher, welcher Ansatz für eine reale Anwendung am besten geeignet ist oder wie ich den besten Ansatz für meine Anwendung ermitteln kann. Also werde ich die Frage offen halten und auf weitere Diskussionen und / oder Meinungen hoffen.
hashcode()
derselben Objektinstanz sollte denselben Wert zurückgeben, es sei denn, in derequals()
Implementierung ändern sich. Mit anderen Worten, wenn Sie drei Felder in Ihrer Klasse haben und Ihreequals()
Methode nur zwei davon verwendet, um die Gleichheit von Instanzen zu bestimmen, können Sie erwarten, dass sich derhashcode()
Rückgabewert ändert, wenn Sie einen der Werte dieses Felds ändern - was in Anbetracht dessen sinnvoll ist dass diese Objektinstanz nicht mehr dem Wert entspricht, den die alte Instanz darstellt.Antworten:
Lesen Sie diesen sehr schönen Artikel zu diesem Thema: Lassen Sie nicht zu, dass der Ruhezustand Ihre Identität stiehlt .
Die Schlussfolgerung des Artikels lautet wie folgt:
quelle
suid.js
ID-Blöcke abgerufen, vonsuid-server-java
denen Sie dann clientseitig abrufen und verwenden können.Ich überschreibe immer equals / hashcode und implementiere es basierend auf der Geschäfts-ID. Scheint die vernünftigste Lösung für mich. Siehe den folgenden Link .
BEARBEITEN :
Um zu erklären, warum das bei mir funktioniert:
quelle
Wir haben normalerweise zwei IDs in unseren Entitäten:
equals()
undhashCode()
insbesondere)Schau mal:
EDIT: um meinen Punkt in Bezug auf Aufrufe der
setUuid()
Methode zu klären . Hier ist ein typisches Szenario:Wenn ich meine Tests ausführe und die Protokollausgabe sehe, behebe ich das Problem:
Alternativ kann ein separater Konstruktor bereitgestellt werden:
Mein Beispiel würde also so aussehen:
Ich verwende einen Standardkonstruktor und einen Setter, aber möglicherweise ist der Ansatz mit zwei Konstruktoren für Sie besser geeignet.
quelle
hashCode
/equals
Methoden für die JVM-Gleichheit undid
für die Persistenzgleichheit? Das ergibt für mich überhaupt keinen Sinn.Object
‚sequals()
zurückkehren würdefalse
in diesem Fall. UUID-basierteequals()
Rückgabetrue
.Ich persönlich habe alle diese drei Strategien bereits in verschiedenen Projekten verwendet. Und ich muss sagen, dass Option 1 meiner Meinung nach die praktikabelste in einer realen App ist. Nach meiner Erfahrung führt das Brechen der Konformität von hashCode () / equals () zu vielen verrückten Fehlern, da Sie jedes Mal in Situationen geraten, in denen sich das Ergebnis der Gleichheit ändert, nachdem eine Entität zu einer Sammlung hinzugefügt wurde.
Es gibt aber noch weitere Möglichkeiten (auch mit Vor- und Nachteilen):
a) hashCode / ist gleich auf der Grundlage einer Reihe von unveränderlichen , nicht null , Konstruktor zugewiesen Felder
(+) Alle drei Kriterien sind garantiert
(-) Feldwerte müssen verfügbar sein, um eine neue Instanz zu erstellen
(-) erschwert die Handhabung, wenn Sie eine davon ändern müssen
b) hashCode / equals basierend auf einem Primärschlüssel, der von der Anwendung (im Konstruktor) anstelle von JPA zugewiesen wird
(+) Alle drei Kriterien sind garantiert
(-) Sie können einfache zuverlässige ID-Generierungsstrategien wie DB-Sequenzen nicht nutzen
(-) kompliziert, wenn neue Entitäten in einer verteilten Umgebung (Client / Server) oder einem App-Server-Cluster erstellt werden
c) hashCode / equals basierend auf einer UUID vom Konstruktor der Entität zugewiesenen
(+) Alle drei Kriterien sind garantiert
(-) Overhead der UUID-Generierung
(-) kann ein geringes Risiko darstellen, dass je nach verwendetem Algorithmus doppelt dieselbe UUID verwendet wird (kann durch einen eindeutigen Index für DB erkannt werden)
quelle
Collection
.Wenn Sie
equals()/hashCode()
für Ihre Sets in dem Sinne verwenden möchten, dass dieselbe Entität nur einmal vorhanden sein kann, gibt es nur eine Option: Option 2. Dies liegt an einem Primärschlüssel für eine Entität per Definition nie ändert (wenn tatsächlich jemand aktualisiert es ist nicht mehr dasselbe Wesen)Sie sollten das wörtlich nehmen: Seit Ihrem
equals()/hashCode()
auf dem Primärschlüssel basieren, dürfen Sie diese Methoden erst verwenden, wenn der Primärschlüssel festgelegt ist. Sie sollten also keine Entitäten in die Gruppe aufnehmen, bis ihnen ein Primärschlüssel zugewiesen wurde. (Ja, UUIDs und ähnliche Konzepte können dazu beitragen, Primärschlüssel frühzeitig zuzuweisen.)Jetzt ist es theoretisch auch möglich, dies mit Option 3 zu erreichen, obwohl sogenannte "Business-Keys" den bösen Nachteil haben, den sie ändern können: "Alles, was Sie tun müssen, ist, die bereits eingefügten Entitäten aus dem Satz zu löschen ( s) und fügen Sie sie erneut ein. " Das ist wahr - aber es bedeutet auch, dass Sie in einem verteilten System sicherstellen müssen, dass dies absolut überall dort erfolgt, wo die Daten eingefügt wurden (und dass Sie sicherstellen müssen, dass das Update durchgeführt wird , bevor andere Dinge auftreten). Sie benötigen einen ausgeklügelten Update-Mechanismus, insbesondere wenn einige Remote-Systeme derzeit nicht erreichbar sind ...
Option 1 kann nur verwendet werden, wenn alle Objekte in Ihren Sets aus derselben Hibernate-Sitzung stammen. Die Hibernate-Dokumentation macht dies in Kapitel 13.1.3 sehr deutlich . Berücksichtigung der Objektidentität :
Es spricht sich weiterhin für Option 3 aus:
Dies ist wahr, wenn Sie
Andernfalls können Sie Option 2 auswählen.
Dann wird die Notwendigkeit einer relativen Stabilität erwähnt:
Das ist richtig. Das praktische Problem, das ich dabei sehe, ist: Wenn Sie keine absolute Stabilität garantieren können, wie können Sie dann Stabilität garantieren, "solange sich die Objekte im selben Set befinden". Ich kann mir einige Sonderfälle vorstellen (wie das Verwenden von Sets nur für ein Gespräch und das anschließende Wegwerfen), aber ich würde die allgemeine Praktikabilität davon in Frage stellen.
Kurzfassung:
quelle
equals
/ verwendenhashCode
.Object
equals und hashCode belassen, da dies nach Ihnenmerge
und der Entität nicht funktioniert .Sie können die Entitätskennung wie in diesem Beitrag vorgeschlagen verwenden . Der einzige Haken ist, dass Sie eine
hashCode
Implementierung verwenden müssen, die immer den gleichen Wert zurückgibt, wie folgt :quelle
Obwohl die Verwendung eines Geschäftsschlüssels (Option 3) der am häufigsten empfohlene Ansatz ist ( Hibernate-Community-Wiki , "Java-Persistenz mit Ruhezustand", S. 398), und dies ist das, was wir meistens verwenden, gibt es einen Hibernate-Fehler, der dies für eifrige Abrufe unterbricht Sätze: HHH-3799 . In diesem Fall kann Hibernate einer Gruppe eine Entität hinzufügen, bevor ihre Felder initialisiert werden. Ich bin mir nicht sicher, warum dieser Fehler nicht mehr Beachtung gefunden hat, da er den empfohlenen Business-Key-Ansatz wirklich problematisch macht.
Ich denke, der Kern der Sache ist, dass equals und hashCode auf einem unveränderlichen Status basieren sollten (Referenz Odersky et al. ), Und eine Hibernate-Entität mit einem Hibernate-verwalteten Primärschlüssel hat keinen solchen unveränderlichen Status. Der Primärschlüssel wird vom Ruhezustand geändert, wenn ein vorübergehendes Objekt persistent wird. Der Geschäftsschlüssel wird auch von Hibernate geändert, wenn ein Objekt während der Initialisierung hydratisiert wird.
Damit bleibt nur Option 1 übrig, die die Implementierungen von java.lang.Object basierend auf der Objektidentität erbt oder einen anwendungsverwalteten Primärschlüssel verwendet, wie von James Brundege in "Lassen Sie den Ruhezustand Ihre Identität nicht stehlen" (bereits in der Antwort von Stijn Geukens erwähnt) vorgeschlagen wurde ) und von Lance Arlaus in " Objektgenerierung : Ein besserer Ansatz für die Integration im Ruhezustand " .
Das größte Problem bei Option 1 besteht darin, dass getrennte Instanzen mit .equals () nicht mit persistenten Instanzen verglichen werden können. Aber das ist in Ordnung; Der Vertrag von equals und hashCode überlässt es dem Entwickler zu entscheiden, was Gleichheit für jede Klasse bedeutet. Lassen Sie also gleich und hashCode von Object erben. Wenn Sie eine getrennte Instanz mit einer persistenten Instanz vergleichen müssen, können Sie explizit eine neue Methode für diesen Zweck erstellen, vielleicht
boolean sameEntity
oderboolean dbEquivalent
oderboolean businessEquals
.quelle
Ich stimme Andrews Antwort zu. Wir machen dasselbe in unserer Anwendung, aber anstatt UUIDs als VARCHAR / CHAR zu speichern, teilen wir sie in zwei lange Werte auf. Siehe UUID.getLeastSignificantBits () und UUID.getMostSignificantBits ().
Eine weitere zu berücksichtigende Sache ist, dass Aufrufe von UUID.randomUUID () ziemlich langsam sind. Daher sollten Sie die UUID nur bei Bedarf träge generieren, z. B. während der Persistenz oder beim Aufrufen von equals () / hashCode ().
quelle
Wie andere Leute, die viel schlauer sind als ich, bereits betont haben, gibt es eine Vielzahl von Strategien. Es scheint jedoch so zu sein, dass die meisten angewandten Designmuster versuchen, ihren Weg zum Erfolg zu hacken. Sie beschränken den Konstruktorzugriff, wenn sie Konstruktoraufrufe mit speziellen Konstruktoren und Factory-Methoden nicht vollständig behindern. In der Tat ist es mit einer klaren API immer angenehm. Aber wenn der einzige Grund darin besteht, dass die Überschreibungen für Gleichheits- und Hashcode mit der Anwendung kompatibel sind, frage ich mich, ob diese Strategien mit KISS (Keep It Simple Stupid) übereinstimmen.
Für mich überschreibe ich gerne Equals und Hashcode, indem ich die ID überprüfe. Bei diesen Methoden muss die ID nicht null sein und dieses Verhalten gut dokumentieren. Somit wird es der Entwicklervertrag, eine neue Entität beizubehalten, bevor sie an einem anderen Ort gespeichert wird. Ein Antrag, der diesen Vertrag nicht einhält, schlägt (hoffentlich) innerhalb einer Minute fehl.
Achtung: Wenn Ihre Entitäten in verschiedenen Tabellen gespeichert sind und Ihr Provider eine Strategie zur automatischen Generierung für den Primärschlüssel verwendet, erhalten Sie doppelte Primärschlüssel für alle Entitätstypen. Vergleichen Sie in diesem Fall auch Laufzeittypen mit einem Aufruf von Object # getClass (), wodurch es natürlich unmöglich wird, dass zwei verschiedene Typen als gleich angesehen werden. Das passt mir zum größten Teil ganz gut.
quelle
Object#getClass()
ist wegen H. Proxies schlecht. Das AnrufenHibernate.getClass(o)
hilft, aber das Problem der Gleichheit von Entitäten unterschiedlicher Art bleibt bestehen. Es gibt eine Lösung mit canEqual , etwas kompliziert, aber brauchbar. Einverstanden, dass es normalerweise nicht benötigt wird. +++ Das Einfügen von eq / hc in die Null-ID verstößt gegen den Vertrag, ist aber sehr pragmatisch.Hier gibt es natürlich schon sehr informative Antworten, aber ich werde Ihnen sagen, was wir tun.
Wir tun nichts (dh nicht überschreiben).
Wenn wir Equals / Hashcode benötigen, um für Sammlungen zu arbeiten, verwenden wir UUIDs. Sie erstellen einfach die UUID im Konstruktor. Wir verwenden http://wiki.fasterxml.com/JugHome für UUID. UUID ist in Bezug auf die CPU etwas teurer, aber im Vergleich zu Serialisierung und Datenbankzugriff billig.
quelle
Ich habe in der Vergangenheit immer Option 1 verwendet, weil ich mir dieser Diskussionen bewusst war und dachte, es sei besser, nichts zu tun, bis ich das Richtige wusste. Diese Systeme laufen alle noch erfolgreich.
Beim nächsten Mal kann ich jedoch Option 2 ausprobieren - unter Verwendung der von der Datenbank generierten ID.
Hashcode und Equals lösen eine IllegalStateException aus, wenn die ID nicht festgelegt ist.
Dadurch wird verhindert, dass subtile Fehler bei nicht gespeicherten Entitäten unerwartet auftreten.
Was halten die Leute von diesem Ansatz?
quelle
Der Business-Keys-Ansatz passt nicht zu uns. Wir verwenden die von der DB generierte ID , die temporäre transiente TempId und überschreiben gleich () / Hashcode (), um das Dilemma zu lösen. Alle Entitäten sind Nachkommen der Entität. Vorteile:
Nachteile:
Schauen Sie sich unseren Code an:
quelle
Bitte beachten Sie den folgenden Ansatz basierend auf der vordefinierten Typkennung und der ID.
Die spezifischen Annahmen für JPA:
Die abstrakte Entität:
Beispiel für eine konkrete Entität:
Testbeispiel:
Hauptvorteile hier:
Nachteile:
super()
Anmerkungen:
class A
undclass B extends A
kann von konkreten Details der Anwendung abhängen.Ich freue mich auf Eure Kommentare.
quelle
Dies ist ein häufiges Problem in jedem IT-System, das Java und JPA verwendet. Der Schmerzpunkt geht über die Implementierung von equals () und hashCode () hinaus und beeinflusst, wie eine Organisation auf eine Entität verweist und wie ihre Clients auf dieselbe Entität verweisen. Ich habe genug Schmerzen gesehen, weil ich keinen Geschäftsschlüssel mehr hatte, bis ich meinen eigenen Blog schrieb , um meine Meinung auszudrücken.
Kurz gesagt: Verwenden Sie eine kurze, lesbare, sequentielle ID mit aussagekräftigen Präfixen als Geschäftsschlüssel, die ohne Abhängigkeit von einem anderen Speicher als RAM generiert wird. Die Schneeflocke von Twitter ist ein sehr gutes Beispiel.
quelle
IMO haben Sie 3 Möglichkeiten, um equals / hashCode zu implementieren
Die Verwendung einer von einer Anwendung generierten Identität ist der einfachste Ansatz, hat jedoch einige Nachteile
Wenn Sie mit diesen Nachteilen arbeiten können , verwenden Sie einfach diesen Ansatz.
Um das Join-Problem zu lösen, könnte man die UUID als natürlichen Schlüssel und einen Sequenzwert als Primärschlüssel verwenden, aber dann könnten Sie immer noch auf die Implementierungsprobleme equals / hashCode in untergeordneten Kompositionsentitäten stoßen, die eingebettete IDs haben, da Sie auf Join-Basis arbeiten möchten auf dem Primärschlüssel. Die Verwendung des natürlichen Schlüssels in der ID der untergeordneten Entitäten und des Primärschlüssels für die Bezugnahme auf das übergeordnete Element ist ein guter Kompromiss.
IMO ist dies der sauberste Ansatz, da er alle Nachteile vermeidet und Ihnen gleichzeitig einen Wert (die UUID) bietet, den Sie mit externen Systemen teilen können, ohne Systeminternale freizulegen.
Implementieren Sie es basierend auf einem Geschäftsschlüssel, wenn Sie erwarten können, dass dies von einem Benutzer eine gute Idee ist, aber auch einige Nachteile mit sich bringt
In den meisten Fällen handelt es sich bei diesem Geschäftsschlüssel um eine Art Code , den der Benutzer bereitstellt, und seltener um eine Zusammenstellung mehrerer Attribute.
IMO sollten Sie nicht ausschließlich einen Geschäftsschlüssel implementieren oder damit arbeiten. Es ist ein nettes Add-On, dh Benutzer können schnell nach diesem Geschäftsschlüssel suchen, aber das System sollte sich beim Betrieb nicht darauf verlassen.
Die Implementierung basierend auf dem Primärschlüssel hat Probleme, aber vielleicht ist es keine so große Sache
Wenn Sie IDs für ein externes System verfügbar machen müssen, verwenden Sie den von mir vorgeschlagenen UUID-Ansatz. Wenn Sie dies nicht tun, können Sie den UUID-Ansatz weiterhin verwenden, müssen dies jedoch nicht. Das Problem bei der Verwendung einer von DBMS generierten ID in equals / hashCode beruht auf der Tatsache, dass das Objekt möglicherweise vor dem Zuweisen der ID zu Hash-basierten Sammlungen hinzugefügt wurde.
Der naheliegende Weg, dies zu umgehen, besteht darin, das Objekt einfach nicht zu Hash-basierten Sammlungen hinzuzufügen, bevor die ID zugewiesen wird. Ich verstehe, dass dies nicht immer möglich ist, da Sie möglicherweise eine Deduplizierung wünschen, bevor Sie die ID bereits vergeben. Um die Hash-basierten Sammlungen weiterhin verwenden zu können, müssen Sie die Sammlungen nach dem Zuweisen der ID einfach neu erstellen.
Sie könnten so etwas tun:
Ich habe den genauen Ansatz nicht selbst getestet, daher bin ich mir nicht sicher, wie das Ändern von Sammlungen in Ereignissen vor und nach dem Fortbestehen funktioniert, aber die Idee ist:
Eine andere Möglichkeit, dies zu lösen, besteht darin, alle Ihre Hash-basierten Modelle nach einem Update / Persist einfach neu zu erstellen.
Am Ende liegt es an Ihnen. Ich persönlich verwende die meiste Zeit den sequenzbasierten Ansatz und verwende den UUID-Ansatz nur, wenn ich eine Kennung externen Systemen aussetzen muss.
quelle
Mit dem neuen Stil
instanceof
von Java 14 können Sie dasequals
in einer Zeile implementieren .quelle
Wenn UUID für viele Menschen die Antwort ist, warum verwenden wir nicht einfach Factory-Methoden aus der Business-Ebene, um die Entitäten zu erstellen und den Primärschlüssel zur Erstellungszeit zuzuweisen?
beispielsweise:
Auf diese Weise erhalten wir vom Persistenzanbieter einen Standardprimärschlüssel für die Entität, und unsere Funktionen hashCode () und equals () können sich darauf verlassen.
Wir könnten auch die Konstrukteure des Autos für geschützt erklären und dann in unserer Geschäftsmethode reflektieren, um auf sie zuzugreifen. Auf diese Weise würden Entwickler nicht die Absicht haben, das Auto mit einem neuen zu instanziieren, sondern durch eine Fabrikmethode.
Wie wäre es damit?
quelle
Ich habe versucht, diese Frage selbst zu beantworten und war nie ganz zufrieden mit den gefundenen Lösungen, bis ich diesen Beitrag gelesen habe und insbesondere DREW. Mir hat gefallen, wie er faul UUID erstellt und optimal gespeichert hat.
Aber ich wollte noch mehr Flexibilität hinzufügen, dh NUR UUID erstellen, wenn auf hashCode () / equals () zugegriffen wird, bevor die Entität zum ersten Mal mit den Vorteilen jeder Lösung fortbesteht:
Ich würde mich sehr über das Feedback zu meiner gemischten Lösung freuen
quelle
In der Praxis scheint Option 2 (Primärschlüssel) am häufigsten verwendet zu werden. Natürliche und unveränderliche Geschäftsschlüssel sind selten. Das Erstellen und Unterstützen von synthetischen Schlüsseln ist zu schwer, um Situationen zu lösen, die wahrscheinlich nie aufgetreten sind. Hier finden Sie aktuelle Frühjahr-data-JPA AbstractPersistable Implementierung (die einzige Sache: für Hibernate Implementierung Gebrauch
Hibernate.getClass
).Beachten Sie nur die Manipulation neuer Objekte in HashSet / HashMap. Im Gegenteil, die Option 1 (
Object
Implementierung bleiben ) wird kurz danach unterbrochenmerge
, das ist eine sehr häufige Situation.Wenn Sie keinen Geschäftsschlüssel haben und einen REALEN Wert haben, um eine neue Entität in der Hash-Struktur zu manipulieren, überschreiben Sie diese
hashCode
auf eine Konstante, wie unten von Vlad Mihalcea empfohlen.quelle
Im Folgenden finden Sie eine einfache (und getestete) Lösung für Scala.
Beachten Sie, dass diese Lösung nicht in eine der drei in der Frage angegebenen Kategorien passt.
Alle meine Entitäten sind Unterklassen der UUIDEntity, daher folge ich dem DRY-Prinzip (Don't-Repeat-Yourself).
Bei Bedarf kann die UUID-Generierung präziser gestaltet werden (durch Verwendung von mehr Pseudozufallszahlen).
Scala Code:
quelle