Eines der Hauptanliegen eines guten ORM ist neben der Behandlung von Identitäten und Gültigkeitsbereichen das Mapping von Sammlungen. Hibernate verfügt in diesem Bereich über eine Menge Fähigkeiten. In diesem Blog werde ich eine Reihe von Collection-Mappings zeigen.
Die Sammlung, die wir abbilden werden, ist sehr einfach;
Klasse Child {
String-Name;
}
Klasse Parent {
String-Name;
Kinder einstellen;
}
Das ist alles. (Nun, eigentlich habe ich eine ganze Menge Code weggelassen, aber das ist das übliche Klempnerhandwerk: ids, Getter und Setter.
Many-to-many
Wir beginnen mit einem exotischen Mapping, das hibernate jedoch bevorzugt:
Hibernate verwendet drei Tabellen, Parent, Child und children, um die Daten und Beziehungen zu speichern. Die Tabelle children wird verwendet, um die Eins-zu-Viel-Verknüpfung in einer Many-to-Many-Form zu speichern. Beachten Sie das unique="true" im many-to-many-Element, das signalisiert, dass es sich nicht um eine "echte" many-to-many-Verknüpfung handelt. Wir führen dasselbe einfache Testszenario mit allen Mappings durch:
- ein Elternteil mit zwei Kindern.
- sehen Sie in einer neuen Sitzung nach.
- ein Kind löschen.
Wenn wir dieses Szenario gegen das Mapping laufen lassen, sehen wir das folgende SQL:
++ insert into Elternteil (name, id) values (?, null) insert into Kind (Name, id) Werte (?, null) insert into Kind (Name, id) Werte (?, null) insert into kinder (id, elt) values (?, ?) insert into kinder (id, elt) values (?, ?) ++ select elternteil0_.id as id1_, elternteil0_.name as name0_1_, kinder1_.id as id3_, kinder2_.id as elt3_, kinder2_.id as id0_, kinder2_.name as name2_0_ from Elternteil elternteil0_ left outer join kinder kinder1_ on elternteil0_.id=kinder1_.id left outer join Kind kinder2_ on kinder1_.elt=kinder2_.id where elternteil0_.id=? ++ delete from kinder wo id=? und elt=? ++
Das Einfügen erfordert nun zwei Anweisungen für jedes Kind. Das scheint ein wenig suboptimal zu sein.
Eins-zu-viele
Lassen Sie uns die folgende Zuordnung ausprobieren, die nur zwei Tabellen verwendet:
Dies führt zu einem weitaus natürlicheren relationalen Modell, mit einem Fremdschlüssel von der Child- zur Parent-Tabelle. Schauen wir uns die Protokollierung an:
++ insert into Elternteil (name, id) values (?, null) insert into Kind (Name, id) Werte (?, null) insert into Kind (Name, id) Werte (?, null) update Child set parent=? where id=? update Child set parent=? where id=? ++ select elternteil0_.id as id1_, elternteil0_.name as name3_1_, kinder1_.elternteil as elternteil3_, kinder1_.id as id3_, kinder1_.id as id0_, kinder1_.name as name4_0_ from Elternteil elternteil0_ left outer join Kind kinder1_ on elternteil0_.id=kinder1_.elternteil where elternteil0_.id=? ++ update Child set parent=null where parent=? and id=? ++
Mmm, immer noch vier Anweisungen, um die beiden Kinder einzufügen. Es gibt eine Einfüge- und eine Aktualisierungsanweisung für dieselbe Zeile für jedes Kind. Das erscheint seltsam, aber wenn Sie die Protokollierung der anderen Zuordnung vergleichen, sehen Sie, dass im Wesentlichen die gleichen Schritte durchgeführt werden: Zuerst werden alle Entitäten eingefügt und dann wird die Verknüpfung hergestellt.
Eins-zu-vielen-nicht-Null
Es wird sogar noch schlimmer, wenn wir die Zuordnung zu ändern:
Jetzt zeigt die Protokollierung:
++ insert into Elternteil (name, id) values (?, null) insert into Kind (Name, Elternteil, id) Werte (?, ?, null) insert into Kind (Name, Elternteil, id) Werte (?, ?, null) update Child set parent=? where id=? update Child set parent=? where id=? ++ select elternteil0_.id as id1_, elternteil0_.name as name5_1_, kinder1_.elternteil as elternteil3_, kinder1_.id as id3_, kinder1_.id as id0_, kinder1_.name as name6_0_ from Elternteil elternteil0_ left outer join Kind kinder1_ on elternteil0_.id=kinder1_.elternteil where elternteil0_.id=? ++ ++
Immer noch vier Anweisungen! Wenn Sie die Protokollierung von org.hibernate.type einschalten, sehen Sie, dass die beiden Aktualisierungen eigentlich überflüssig sind, da die parent-id's bereits durch die insert-Anweisungen korrekt ausgefüllt wurden.
- Komponenten verwenden.
- Bilden Sie die Beziehung bidirektional und invers ab.
Komponente
Wenn Sie die untergeordneten Objekte als Komponenten abbilden, hat dies Auswirkungen auf die Verwendung Ihrer Klassen. Die Objekte verlieren ihre Identität, was das relationale Modell betrifft. Das bedeutet, dass kein anderes Objekt (außer dem Elternteil) einen Verweis auf das Kind haben kann, da das Kind keine ID-Spalte im relationalen Modell hat und daher keine Fremdschlüssel zu einer solchen Spalte erstellt werden können. Das Kind kann jedoch Eins-zu-Eins- oder Viele-zu-Eins-Verknüpfungen zu anderen Entitäten haben. Außerdem wird der Lebenszyklus der untergeordneten Entitäten mit dem Lebenszyklus der übergeordneten Entität verknüpft. Das Mapping sieht nun wie folgt aus:
Schauen wir uns die Protokollierung an:
++ insert into Elternteil (name, id) values (?, null) insert into Kinder (Eltern, Name) Werte (?, ?) insert into Kinder (Eltern, Name) Werte (?, ?) ++ select elternteil0_.id as id0_, elternteil0_.name as name7_0_, kinder1_.elternteil as elternteil2_, kinder1_.name as name2_ from Elternteil elternteil0_ left outer join kinder kinder1_ on elternteil0_.id=kinder1_.elternteil where elternteil0_.id=? ++ delete from children where parent=? and name=? ++
Jetzt gibt es nur noch drei Anweisungen, um das Parent und die Children einzufügen. Beachten Sie, dass bei dieser Zuordnung, im Gegensatz zu den anderen, das Kind tatsächlich aus dem permanenten Speicher entfernt wird, wenn es aus der Sammlung entfernt wird.
Umgekehrt
Die Einschränkungen bei der Verwendung könnten für die meisten Anwendungen zu groß sein. Wenn das Kind eine vollwertige Entität sein muss, kann die Beziehung bidirektional und invers abgebildet werden. Dadurch wird das Domänenmodell geändert und eine Assoziation vom Kind zum Elternteil hinzugefügt.
Klasse Child {
String-Name;
Elternteil;
}
Klasse Parent {
String-Name;
Kinder einstellen;
}
Die Zuordnung sieht nun wie folgt aus:
Die Eigenschaft parent auf der Child-Entität wird nun als Many-to-One-Assoziation abgebildet und die Sammlung children auf der Parent-Entität wird als inverse="true" abgebildet. Diese Konfigurationsoption ist ziemlich schwer zu verstehen. Im Prinzip bedeutet sie, dass die Assoziation bidirektional gemappt wird und dass die andere Seite der Assoziation führend ist. In der Tat ist die Assoziation nun Teil der untergeordneten Entität anstelle der übergeordneten Entität. Die Protokollierung sieht nun folgendermaßen aus:
++ insert into Elternteil (name, id) values (?, null) insert into Kind (Name, Elternteil, id) Werte (?, ?, null) insert into Kind (Name, Elternteil, id) Werte (?, ?, null) ++ select elternteil0_.id as id1_, elternteil0_.name as name9_1_, kinder1_.elternteil as elternteil3_, kinder1_.id as id3_, kinder1_.id as id0_, kinder1_.name as name10_0_, kinder1_.elternteil as elternteil10_0_ from Elternteil elternteil0_ left outer join Kind kinder1_ on elternteil0_.id=kinder1_.elternteil where elternteil0_.id=? ++ update Child set name=?, parent=? Wo id=? ++
Da die Assoziation nun Teil der untergeordneten Entität ist, wird sie zusammen mit der untergeordneten Entität eingefügt und es ist nur eine Einfügung (oder Aktualisierung) erforderlich.
Vollverwaiste
Bei keiner der obigen Zuordnungen, die Child als separate Entität zuordneten, wurden die Childs gelöscht, wenn sie aus dem Set entfernt wurden. Bei der Zuordnung eins-zu-viel-nicht-null führte das Löschen des Childs nicht einmal zu einer DML-Aktion. Warum ist das so? Die Antwort liegt in der Kaskadeneinstellung der Assoziation. Die Einstellung
++ insert into Elternteil (name, id) values (?, null) insert into Kind (Name, id) Werte (?, null) insert into Kind (Name, id) Werte (?, null) insert into kinder (id, elt) values (?, ?) insert into kinder (id, elt) values (?, ?) ++ select elternteil0_.id as id1_, elternteil0_.name as name11_1_, kinder1_.id as id3_, kinder2_.id as elt3_, kinder2_.id as id0_, kinder2_.name as name13_0_ from Elternteil elternteil0_ left outer join kinder kinder1_ on elternteil0_.id=kinder1_.id left outer join Kind kinder2_ on kinder1_.elt=kinder2_.id where elternteil0_.id=? ++ delete from kinder wo id=? und elt=? delete from Child where id=? ++
Wie erwartet werden also zwei Löschvorgänge verwendet, um die Assoziation und das Kind beim Entfernen aus der Menge zu löschen. Bei der Eins-zu-Viel-Zuordnung ist die Situation kaum besser.
++ insert into Elternteil (name, id) values (?, null) insert into Kind (Name, id) Werte (?, null) insert into Kind (Name, id) Werte (?, null) update Child set parent=? where id=? update Child set parent=? where id=? ++ select elternteil0_.id as id1_, elternteil0_.name as name14_1_, kinder1_.elternteil as elternteil3_, kinder1_.id as id3_, kinder1_.id as id0_, kinder1_.name as name15_0_ from Elternteil elternteil0_ left outer join Kind kinder1_ on elternteil0_.id=kinder1_.elternteil where elternteil0_.id=? ++ update Child set parent=null where parent=? and id=? delete from Child where id=? ++
Die Aktualisierung (vor dem Löschen) wird verwendet, um die Assoziation zu löschen, was ziemlich redundant erscheint, da dieselbe Zeile mit der nächsten Anweisung gelöscht wird. Wenn die Eins-zu-viel-nicht-null-Zuordnung verwendet wird, wird die redundante Aktualisierung entfernt:
++ insert into Elternteil (name, id) values (?, null) insert into Kind (Name, Elternteil, id) Werte (?, ?, null) insert into Kind (Name, Elternteil, id) Werte (?, ?, null) update Child set parent=? where id=? update Child set parent=? where id=? ++ select elternteil0_.id as id1_, elternteil0_.name as name16_1_, kinder1_.elternteil as elternteil3_, kinder1_.id as id3_, kinder1_.id as id0_, kinder1_.name as name17_0_ from Elternteil elternteil0_ left outer join Kind kinder1_ on elternteil0_.id=kinder1_.elternteil where elternteil0_.id=? ++ delete from Child where id=? ++
Dies ist auch der Grund, warum die Assoziation nicht gelöscht werden kann, ohne auch die untergeordnete Entität zu entfernen. Die Spalte mit dem Fremdschlüssel kann aufgrund der Einschränkung nicht mit NULL gefüllt werden, so dass die Assoziation nur gelöscht werden kann, wenn auch die untergeordnete Entität gelöscht wird. Die letzte Zuordnung ist die Zuordnung von einer zu einer anderen Entität:
++ insert into Elternteil (name, id) values (?, null) insert into Kind (Name, Elternteil, id) Werte (?, ?, null) insert into Kind (Name, Elternteil, id) Werte (?, ?, null) ++ select elternteil0_.id as id1_, elternteil0_.name as name18_1_, kinder1_.elternteil as elternteil3_, kinder1_.id as id3_, kinder1_.id as id0_, kinder1_.name as name19_0_, kinder1_.elternteil as elternteil19_0_ from Elternteil elternteil0_ left outer join Kind kinder1_ on elternteil0_.id=kinder1_.elternteil where elternteil0_.id=? ++ delete from Child where id=? ++
Im Grunde ist diese Zuordnung genau dasselbe wie die Komponenten-Zuordnung, aber mit dem Child als Entität.
Fazit
Wir haben uns 9 verschiedene Hibernate-Mappings für eine einfache Eins-zu-Viel-Verknüpfung angesehen und uns auf die Konsequenzen für die ausgegebenen DML-Anweisungen konzentriert. Es gibt einige inkonsistente Konfigurationen, die für eine Anwendung schädlich sein können (z.B. die one-to-many-not-null Assoziation, die das Entfernen von Childs nicht erlaubt).
Ich denke, dass Komponenten bei der Verwendung von Hibernate generell so oft wie möglich verwendet werden sollten. Sie machen synthetische Schlüssel und Spalten überflüssig und die Leistung der DML-Anweisungen und des Sitzungscaches ist besser, da die Komponenten nicht (separat) von der Sitzung verwaltet werden.
Verfasst von
Maarten Winkels
Unsere Ideen
Weitere Blogs
Contact



