ich wollte mal wieder ein paar Meinungen und Erfahrungen von euch einholen. Wie implementiert ihr, wenn ihr mit JPA arbeitet, die equals() und hashcode() Methoden? Grundlegend gibt es ja zwei Ansätze:
1. Vergleich auf die ID
Man berücksichtigt nur den PrimaryKey. Damit ist die Instanz eindeutig zu identifizieren. Collections funktionieren bei persistierten Objekten, z.B. wenn man eine 1:n Beziehung mit einem Set abbilden möchte. Instanzen im „detached“ Status sind nicht vergleichbar, was z.B. bei o.g. Set ein Problem macht, wenn man die referenzierten Objekte über ein Cascade mitspeichern möchte. Vereinzelt habe ich auch schon Lösungen gefunden, die hier mit einer „Anwendungsweiten“ Id arbeiten und mit einer Persistenz-ID, finde ich eigentlich einen ganz interessanten Ansatz.
2. Vergleich auf die „wichtigen“ Felder
Quasi so, wie es von einer IDE vorgeschlagen wird. Theoretisch kann es vorkommen, dass im JPA Cache zwei verschiedene Instanzen mit der selben ID herumgeistern (wenn man nicht aufpasst).
3. Ich mache was ganz anderes
Das müsstet ihr dann in die Antworten schreiben…
Hat sich bei euch eine der Lösungen schon mal als Stolperstein herausgestellt? Was für Erfahrungen habt ihr gemacht?
[quote=inv_zim;89549]2. Vergleich auf die “wichtigen” Felder
Quasi so, wie es von einer IDE vorgeschlagen wird. Theoretisch kann es vorkommen, dass im JPA Cache zwei verschiedene Instanzen mit der selben ID herumgeistern (wenn man nicht aufpasst).[/quote]
So. Steht so in den Dokus (bspw. zu Hibernate) und macht für aus Java-Sicht auch nur so Sinn. Denn das ist genau der Zweck von equals(). Die Situation, dass verschiedene Instanzen mit derselben Id (nicht nur im Cache) sondern in der ganzen Anwendung, bei Multiuser Szenarien gar mehreren Anwendungen rumgeistern ist durch entsprechende Locking-Stategien aufzulösen. Selbst ein Einbeziehen der Id in equals schafft hier nur in bestimmten Szenarien Abhilfe.
ich vergleiche auch die wichtigen Felder, ist auch hilfreich um zu gucken ob ich ein Objekt bereits in der Datenbank habe und nicht unnötig Daten ansammle. (Die ID’s werden ja erst beim Speichern vergeben.)
Da würde ich jetzt eher auf Unique Felder in der Datenbank setzen?
@nillehammer :
Dass es so in der Doku steht wusste ich gar nicht (benutze Hibernate zwar nicht, aber ich muss gestehen, in die EclipseLink Doku habe ich da auch noch nicht geschaut). Ich hatte ja auch bei Möglichkeit 2 dazugeschrieben “wenn man nicht aufpasst”.
folgendes Beispiel: es gibt Teile, diese Teile haben eine, ID, eine Nummer, eine Version, einen Status, einen Ersteller usw.
zwei Teile sind in der Anwendung gleich wenn Nummer, Version und Status gleich sind. Mache ich aus diesen Feldern Uniques, kann ich keine unterschiedlichen Versionen eines Teils speichern.
Ich implementiere auch auf die Art Nr. 2. Hauptsächlich haben mich dazu die Argumente in der Hibernate Dokumentation bewogen (Detached Status => ID ggf. undefiniert).
Aus der Sicht von Domain Driven Development ist das aber der falsche Weg, zumindest, wenn die JPA Entities Entitäten im Domänenmodell darstellen. Denn Entitäten werden über ihre Identität und nicht über den Wert von ihren Eigenschaften identifiziert. Das funktioniert aber nur dann sinnvoll, wenn man konsequent natürliche Primärschlüssel wählt (was ebenfalls der “Hibernate Best Practices” widerspricht - dort wird auch für den Fall eines nativen PK die Verwendung eines generierten Schlüssels empfohlen).
Man sieht also, dass ich mir auch ein wenig uneins bin, wie man für JPA Entities die equals() und hashCode() Methoden definieren soll…
@pl4gu33 : wenn du die ID in den Vergleich mit einbeziehst, dann vereinst du die Nachteile beider Wege in den beiden Methoden (detached: ggf. falsches Ergebnis; ansonsten: ggf. überspezifizierter Vergleich). Das würde ich also auf gar keinen Fall so machen.
Zu den “mehrere Felder überspannenden Bedingungen” sind zusammengesetzte Constraints der einzig richtige Weg, weil sie dadurch bereits durch die Datenbank sichergestellt werden. Das ist auch deutlich effizienter, als das in Java manuell zu implementieren (es werden Indizes angelegt, die einen “schnellen” Lookup in konstanter bzw. logarithmischer Zeit ermöglichen).
Naja bisher hatte ich noch keine großen Probleme damit… Hatte das „damals“ als Beispiel in einem JPA Buch bzw. im Studium gelernt und ich habs dann irgendwann immer weiter übernommen und manchmal isses dann ja so, man lernt erst dazu, wenn man auf die Nase fällt … aber du hast schon recht und werde das in Zukunft auch anders lösen
(Die Stackoverflow Artikel dazu habe ich auch erst aufgrund des Beitrags gelesen)
Entities sind per Definition nur per ID eindeutig, richtig?
Das man die ID erst durch das Persistenzframework vergeben laesst beruht schlicht auf Faulheit (und schlechter Doku!), dann wird naemlich ein technischer Nebenaspekt ploetzlich zum fachlichen Problem…
In einem Satz:
Entities am besten per ID vergleichen, aber IDs am besten nicht erst durch die Persistenz erzeugen lassen!
Und wie stellst Du fest, ob eine Entity schon persistiert ist? So etwas benötige ich häufiger. Natürlich kann man dann immer in die DB schauen, aber ist das sinnvoll?
Ich habe gerade noch einmal für Eclipselink nachgeschlagen: Eclipselink benutzt definitiv explizit nicht equals() und hashcode() sondern vergleicht nur über die angegebene Identity, d.h. egal was man tut, die Persistenzschicht betrifft es eh nicht.
Dann ist natürlich die Frage, wie ich z.B. einen Kunden explizit identifizieren kann. Eine Person hat streng genommen kein einziges Attribut, welches sich nicht ändern kann. M.M.n. müsste man also beim Erstellen des Objekts innerhalb der Anwendung eine eindeutige ID erstellen, die rein für die Identifizierung des Objektes benutzt wird und nichts mit JPA zu tun hat. Einzig und allein auf diese vergleicht man in der Equals Methode.
Eben, falls man das braucht… oft nur um zu entscheiden wie in euqals/hascode gearbeitet werden soll
DDD sagt aber nicht dass man wieder fachliche Schluessel nehmen sollte
„nicht sagende IDs“ sind einfach nur IDs die eindeutig sind, nicht mehr, nicht weinger. Diese sind nicht schlecht, ganz im Gegenteil.
Es geht dabei ja nicht darum, dass man keine generierten IDs mehr verwendet, sondern darum, dass diese erst vom Persistenzframework erstellt werden, denn das hat viele Nachteile wie man an der equals/hashcode Diskussion sehen kann.
Einen „IdGenerator“ zu schreiben ist sehr simpel, AtomicInteger/AtomicLong welcher mit dem hoechsten ID Wert aus der DB + 1 beim starten der Anwendung initialisiert wird, fertig, machen ORMs nicht anders.
UUID gehen auch, haben aber auswirkungen auf RDBMS.
Dieser IdGenerator kann dann in die Factory injiziert werden, einen pro Entity, oder einen fuer alle Entities.
Ist wirklich nicht schwer wenn man sich mental vom Konzept loest dass die Persistenz das machen sollte…
[QUOTE=maki]DDD sagt aber nicht dass man wieder fachliche Schluessel nehmen sollte
„nicht sagende IDs“ sind einfach nur IDs die eindeutig sind, nicht mehr, nicht weinger. Diese sind nicht schlecht, ganz im Gegenteil[/QUOTE]
Die „nichtssagenden“ IDs haben den entscheidenden Vorteil, dass sie sich definitiv nie ändern.
Allerdings ist es sinnvoll, IDs zu „recyclen“, also den PK (der in diesem Fall dann gleichzeitig ein FK ist) von einer Gruppe von Entities gleich zu setzen. Ich hab den Begriff grad nicht im Kopf, wie Evans es genannt hatte, dass der Zugriff auf bestimmte Entities über „Einstiegspunkte“ zu erfolgen hat. Dort ist es sinnvoll, die ID vom „Einstiegspunkt“ für alle „untergeordneten“ Entities zu verwenden.
Die sog. „Root Entities“
Sind sehr nuetzlich beim strukturieren, braucht ja nicht fuer jede Entity unbedingt Repositories/DAOs, vor allem wenn die Entity ohne ihre Root, also die uebergeordnete Entity, nutzlos ist.
Slebst wenn DDD in reinform fast unmoeglich ist in der heutigen Industrie, gibt es sehr viele Konzepte daraus die sehr gut sind IMHO
Ist ja wurscht ob es ein Primaerschluessel ist oder sonstwas, wenn es in equals/hashcode verwendet werden soll, sollte es unabhaengig davon sein ob schon persistiert wurde oder nicht, sonst gibt es verkruepelte equals Implementierungen, Sets die die Entities nach dem persistieren nicht mehr hergeben weil sich equals & hashcode nach dem persistieren anders verhalten, usw.
Ob die eindeutige ID von einer DB kommt oder nicht ist erstmal wurscht, solange er eindeutig ist.
Wenn ich IDs haben kann ohne erst persisitieren zu muessen ist das ein Vorteil, weil ich dann nicht darauf achten muss ob schon persistiert wurde wenn ich damit arbeiten moechte.
Ansonsten ist das kein DDD Proble,m hat jede ORM/JPA/Hibernate Anwendung.
User erstellt neue Entities die Transient sind und will damit erst „was machen“ bevor er speichert.
Ansonsten:
Wie macht das Hibernate bzw. die DB mit den eindeutigen IDs?
Das Problem gab es ja schon immer, ich kenne das noch aus der guten alten Zeit, als man J2EE und Value Objects brauchte und so.
Früher sahen die equals-Implemtierungen dann immer so aus, dass man zuerst geschaut hat, ob ein PK vorhanden ist und falls nicht eben die Felder inhaltlich verglichen hat. War immer schon ungenau bis fehlerhaft…Manchmal gibt es auch einen ‘inhaltlichen’ Primärschlüssel, der bei der Objekterstellung schon vor der Persistierung vorhanden sein muss (Artikelnummer, etc). Aber hilft eine neue, beim Client erzeugte künstliche ID wirklich weiter?
Schon klar, aber dann schwirren in Zukunft drei Sachen rum:
1 - die Gleichheit der Software-seitigen IDs
2 - die Gleichheit der von der DB erzeugten PKs (im Idealfall)
3 - die inhaltliche Gleichheit der Attribute
wird doch dann immer verwirrender…Was wär denn ein konkreter Fall für mit einem noch nicht persistierten Objekt was machen?
Weil ploetzlich immer diese ID verwendet werden kann um ein Objekt eindeutig zu identifizieren, nicht mal so (ID Vergleich) und mal so (Vergleich von Werten)…
[quote=Bleiglanz;97578]1 - die Gleichheit der Software-seitigen IDs
2 - die Gleichheit der von der DB erzeugten PKs (im Idealfall)
3 - die inhaltliche Gleichheit der Attribute[/quote]
1 - Dazu muessen sie nur eindeutig sein.
2 - Die DB koennte theoretisch nochmal eine eigene ID vergeben, muss aber nicht, koennte auch nur die ID nehmen, UUIDs waeren gut fuer die Java seite, aber RDBMS wollen die nicht so gerne als PK, zumindest bricht bei vielen dann die Performance ein.
3 - “Die inhaltliche Gleichheit der Attribute” ist fuer Entities vollkommen egal, es kommt nur auf die ID an. Attribute zu vergleichen ist nur ein Workaround wenn es noch keine ID gibt weil diese erst von der DB erzeugt wuerde.
Alles in allem geht es auch darum, Domaenenspezifische Sachen wie eindeutige IDs auch ohne die Persitenzschicht da sein sollten.
Kompliziert wird es IMHO nur, wenn es um verteilte Systeme geht, bei denen mann ungern eine einzige zentrale Stelle haette die IDs vergibt.
Fuer “normale” Server Anwendungen ist das ziemlich trivial IMHO: ID Generator basierend auf einem AtomicInteger oder AtomicLong.
Initialisert wird mit der hoechsten ID + 1 aus der DB, macht Hibernate auch nicht anders.
Oder man macht es wie Hibernate/JPA und nimmt eine Tabelle nur fuer IDs (GenerationType.TABLE).
Das ist eigentlich genau meine Frage. Nehmen wir an, ich habe was Persistenzwürdiges (BUCH, ARTIKEL, TODOITEM, …). Und mache da softwareseitig so rum, mit schönen POJOs usw. - Wenn ich jetzt bei der Gleichheit immer nur ausschließlich die schöne erzeugte ID verwende - was ist dann der Vorteil? Wenn zwei Objekte irgendwo auf einer anderen Baustelle erzeugt werden, dann sind sie immer verschieden, die unterschiedliche ID sagt einzig und allein aus, dass zweimal die Objekterzeugung stattgefunden hat (mit welchen Attributen auch immer) - ist das wirklich eine so wichtige Information?
Das hätte doch nur Sinn, wenn ich mir beim rummachen die ID merke, damit ich - wenn mir das Ding später mal übern weg läuft - es wiedererkennen kann. Irgendwie sehe ich nicht ganz den Vorteil, das macht die Software allenfalls komplizierter.
Warum nicht eine noch nicht persistente Entity als flüchtiges herumschwirrendes Ding betrachten und auch so behandeln - ganz “inhaltlich”.