Samstag, September 29, 2018
Links der Woche (Schwerpunkt Domain Driven Design)
Donnerstag, September 13, 2018
Links der Woche
- Mathematik Apps für das Smartphone. Enthält eine Sammlung von Spielen/Rätseln rund um das Thema Mathematik/Geometrie.
- Was für Dinge benötigt man, wenn man einen Videokanal eröffnen möchte? Hier gibt es ein paar Tipps.
- Die Öffi App ist zurück im Play Store.
- Test Driven Development noch einmal erklärt.
- Ein Artikel auf martinfowler.com über die Aufspaltung eines Datenmonolithen.
- Ein Kurzanleitung wie 1:N Beziehungen in der JPA Welt abzubilden sind.
- Visual Studio Code vs Postman. Ein Rest-Client integriert in der IDE. Das hört sich sehr interessant an. Die Extension findet ihr hier.
Sonntag, September 09, 2018
JPA Entities, equals und hashCode
Wer hat noch nicht mit seinen Kollegen über die ‘korrekte’ Implementierung
von #hashCode()
und #equals()
diskutiert? Hier kommt meine Zusammenfassung.
Grundlagen
Zunächst die Grundlagen: Welche Bedingungen stellt Java an #hashCode()
und #equals()
?
Da wären:
- reflexiv: Für alle x gilt:
x.equals(x)
liefert immer true zurück. - symmetrisch: Für alle x,y gilt:
x.equals(y) == y.equals(x)
- transitiv: Für alle x,y,z gilt:
x.equals(y)
undy.equals(z)
dann giltx.equals(z)
- konsistent: Das Ergebnis von
#hashCode()
und#equals()
muss während der gesamten Lebensdauer eines Objekts gleich sein. Konsistenz kann eigentlich nur für unveränderliche Objekte gelten. Oder sagen wir mal so: Es wäre vermutlich ganz praktisch. Allerdings gibt es hier Tricks. Wie z.B. das die Methode#hashCode()
eine Konstante zurückliefert. Oder das sich der HashCode nur aus den fachlichen Schlüsseln zusammensetzt.
Das sollte als Einführung reichen. In der JPA Community habe ich 3 Stilrichtungen gefunden, die praktikabel erscheinen. Jede mit gewissen Vor- und Nachteilen. Meine persönliche Präferenz, um das mal vorweg zu nehmen, ist die, die den wenigsten Code erzeugt.
Konsens oder ‘no-go’
Die drei Experten (Gavin King, Vlad Mihalcea, Mark Struberg) sind sich in einer Sache
einig. Habe ich eine JPA Entity sollte ich auf gar keinen Fall #equals()
und
#hashCode()
über alle Eigenschaften der Entity ermitteln. Denn wenn ich das tue,
was hätte das für die geforderte Eigenschaft ‘konsistent’ zur Folge? Genau. Die Entity müsste
unveränderlich sein. D.h. die Code-Generatoren der bekannten IDEs und die
Klassen EqualsBuilder
oder HashCodeBuilder
aus Apache Commons sind tabu.
Variante 1. Die Struberg-Methode | Die Nicht-Implementierung.
Struberg hat einen eigenen Blog. Allein
der ist schon empfehlenswert. Der für mich interessante Artikel findet sich
hier.
Die sogenannte Struberg Variante empfiehlt, auf #equals()
und #hashCode
zu
verzichten. Hauptgrund: Die anderen Varianten (die ich später aufzähle) sind
kompliziert und fehleranfällig. Zu dem stellt sich die Frage, benötige
ich überhaupt eine spezielle #hashCode()
Methode für meine Entity?
I.d.R wird dann angeführt, dass die Entities in einem Set
gespeichert
werden. Also z.B.
@OneToMany
private Set others;
Aber selbst hier ist die Defaut-Implementierung aus Object
absolut ausreichend.
Das wird durch zwei Annahmen untermauert. Das erste Argument: Der EntityManager
selbst sorgt für die Eindeutigkeit der Objekte im Set
. Das zweite Argument,
in dem Artikel nicht aufgeführt, aber dennoch interessant: Die Datenbank selbst
sorgt mit Unique-Constraints dafür, dass die Elemente im Set
eindeutig sind.
Allerdings richtig ist der Einwand, dass in diesem Fall das Set
nicht mehr
selbst in der Lage ist, doppelte Objekte zu erkennen. Aber vielleicht ist in diesem
Fall die Validierung in der Geschäftslogik zu lückenhaft?
Wichtig: Das abwägen von Aufwand und Nutzen. Als ‘so-wenig-Code-wie-möglich’ Liebhaber bin ich auf alle Fälle ein Fan dieser Variante.
Variante 2. Hibernate Dokumentation | Der fachliche Schlüssel
In der Hibernate Dokumentation
findet sich die Variante 2. Eine Empfehlung lautet aber auch hier,
eventuell auf die Implementierung von #hashCode()
und #equals()
zu verzichten.
Will oder kann man das nicht, sollte man den natürlichen Schlüssel der Entity bei der
Gestaltung verwenden. Erwähnt wird ebenso, wie bei Struberg oben, dass der
EntityManager oder die Session dafür sorgt, dass gleiche Objekte nur einmal
instanziert werden. Das Problem kann nur dann auftreten, wenn gleiche Objekte
über verschiedene Sessions aus der Datenbank gelesen werden. In diesem Fall
würde das #equals()
, #hashCode()
aus Object
zu kurz greifen. Doch
man muss sich die Frage stellen, habe ich so eine Situation in meiner Anwendung?
In der Dokumentation findet sich der Hinweis, dass das heranziehen allein
der ID
aus der Entity i.d.R. nicht ausreicht. Bei Neuinstanzierung einer
Entity ist diese Eigenschaft nicht gesetzt und wird erst nach einem flush
oder commit
der Session in der Entity gesetzt. Damit hätten wir ebenfalls
eine Verletzung der Konsistenz. Also bleibt nur der natürliche oder
fachliche Schlüssel, der sich nicht ändert und von Anfang an bekannt ist.
In dem Beispiel ist es die ISBN eines Buches. Ich verzichte darauf, dass
Beispiel hier wiederzugeben. Der interessierte folgt einfach dem
Link.
Variante 3. Die Vlad-Methode
Vlad ist der Meinung, dass man #hashCode()
und #equals()
in jedem Fall
implementieren sollte. Er hat für Entities einen sehr guten Test geschrieben,
der entscheidet, ob die gewählte Implementierung den Forderungen von
#hashCode()
und #equals()
und den Anforderungen der JPA Spezifikation
entspricht. Siehe dafür in seinem
GitHub
Account oder direkt in der
Klasse AbstractEqualityCheckTest.java.
Ohne korrekte #hashCode()
und #equals()
Implementierung wird dieser
Test einen Fehler reporten.
In seinem Blog-Artikel unterscheidet Vlad zwischen zwei Typen von Schlüsseln (Identifiables):
Natürliche/Fachliche Schlüssel oder UUIDS
Beiden gemein ist, dass sie bereits vor dem #flush()
(Persistierung) in
die Datenbank bekannt sind und der Entity zugewiesen werden. In diesen Fällen sollte eine Implementierung so aussehen:
@Entity(name = "Book")
@Table(name = "book")
public class Book
implements Identifiable<Long> {
@Id
@GeneratedValue
private Long id;
private String title;
@NaturalId
private String isbn;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false;
Book book = (Book) o;
return Objects.equals(getIsbn(), book.getIsbn());
}
@Override
public int hashCode() {
return Objects.hash(getIsbn());
}
//Getters and setters omitted for brevity
}
D.h. die Implementierung zieht nur den fachlichen Schlüssel. Alle anderen Eigenschaft werden ignoriert.
Datenbank generierte Schlüssel
In den anderen Fällen, in denen der Schlüssel von der Datenbank generiert wird, empfiehlt er die folgende Implementierung:
@Entity
public class Book implements Identifiable<Long> {
@Id
@GeneratedValue
private Long id;
private String title;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Book)) return false;
Book book = (Book) o;
return id != null && id.equals(o.id);
}
@Override
public int hashCode() {
return 31;
}
//Getters and setters omitted for brevity
}
Wichtig: Die #hashCode()
Methode liefert eine Konstante zurück. Damit ist sichergestellt,
dass der gleiche HashCode über alle Entity-Persistenz-Zustände geliefert wird.
Weitere Erkenntnisse
So, having a cache is really a great idea, but please do not store JPA entities in the cache. At least not as long as they are managed.
Der Dirty-Check von Hibernate funktioniert NICHT über die
#equals()
Methode. Hibernate verwaltet ein Duplikat der Entity und fährt über dieses Duplikat einen eigenen Vergleich. Siehe Hibernate dirty check.
Another way is to generated a UUID in the constructor or the getId() method. But this is pretty performance intense and also not very nice to handle on the DB side (large Strings as PK consume a lot more storage in the indexes on disk and in memory).
Referenzen
- https://vladmihalcea.com/the-best-way-to-implement-equals-hashcode-and-tostring-with-jpa-and-hibernate/
- https://vladmihalcea.com/how-to-implement-equals-and-hashcode-using-the-jpa-entity-identifier/
- https://vladmihalcea.com/hibernate-facts-equals-and-hashcode/
- http://docs.jboss.org/hibernate/orm/5.3/userguide/html_single/Hibernate_User_Guide.html#mapping-model-pojo-equalshashcode
- https://github.com/vladmihalcea/high-performance-java-persistence/blob/master/core/src/test/java/com/vladmihalcea/book/hpjp/hibernate/equality/AbstractEqualityCheckTest.java#L55
- https://struberg.wordpress.com/2016/10/15/tostring-equals-and-hashcode-in-jpa-entities
- https://courses.vladmihalcea.com/?utm_source=blog&utm_medium=banner&utm_campaign=article
- Das gleich Buch wie oben, aber bei amazon.de
- Thoughts on Java
AssertJ und java.util.List
AssertJ hat eine praktische Möglichkeit, Listen in JUnit Tests abzuprüfen. Insbesondere, wenn in der Liste komplexe Objekte abgelegt sind, s...
-
Wer das erste mal einen klassischen Rest-Service mit Spring schreibt, also einen @RestController implementiert, der JSON (application/json;...
-
Die Tastensteuerung im VI kann ich mir nur grob granular merken. Deswegen dieser Versuch, die Sachen einmal aufzuschreiben und natürlich neb...
-
DOS-Box: Next Generation Wer als Entwickler behauptet, er arbeitet unter Windows, der erntet von seinen Linux/MacOS Kollegen ein müdes läch...