Blog

Verwirrende Syntax der Hibernate-Konfiguration

Maarten Winkels

Aktualisiert Oktober 23, 2025
7 Minuten

Heute habe ich einige Stunden damit verbracht, einen Hibernate-Fehler in unserer Anwendung zu beheben. Ich habe die Konfiguration ein wenig geändert und es schien, dass Hibernate damit nicht umgehen konnte. Ich hatte sogar einen Fehlerbericht in Hibernate JIRA gefunden, der die gleiche Situation beschrieb. Ich war kurz davor, die Quellen herunterzuladen und zu versuchen, das Problem in Hibernate zu beheben... Es stellte sich heraus, dass es einen Fehler in unserer Konfiguration gab! Das heißt, das Modell, das wir konfigurieren wollten, konnte nicht auf einfache Weise konfiguriert werden. Aus dem JIRA-Problem geht hervor, dass es noch mehr Leute gibt, denen es schwer fällt, die richtige Konfiguration für diese Situation zu finden. Lassen Sie mich versuchen, ihnen mit einem kleinen Beispiel zu helfen.

Problem Angenommen, wir haben eine Master-Klasse, die eine Reihe von Children und eine Peer-Eigenschaft hat. Die Menge der Children wird als eine Eins-zu-viele-Assoziation und die Peer-Eigenschaft als eine Eins-zu-eins-Assoziation modelliert. Außerdem werden beide Assoziationen bidirektional zugeordnet. Dies ist eine ziemlich einfache Zuordnung:

  

Dies führt nun zu einem sehr einfachen Datenbankschema, wie in der folgenden Abbildung dargestellt. In manchen Fällen möchten Sie die Tabellen Peer und Child in zwei (verbundene) Tabellen aufteilen. Eine besondere Situation, in der dies sinnvoll sein kann, ist, wenn die Klasse Teil einer Hierarchie ist und eine gemischte Strategie aus Tabelle pro Hierarchie und Tabelle pro Klasse verwendet wird. Das Datenbankschema, das wir anstreben, ist unten abgebildet.

Lösung - Erster Versuch Wie ändern Sie nun das Mapping? Eine logische Idee wäre, dass wir, da wir die Peer- und Child-Klasse in zwei Tabellen aufgeteilt haben, eine <join> in der Konfiguration für diese beiden Klassen benötigen. Die Konfiguration sieht dann wie folgt aus:

  

Diese Zuordnung scheint sehr logisch zu sein. Die Änderungen wurden in den Tabellen PEER und CHILD vorgenommen und wir haben die Zuordnung für diese Tabellen geändert. Es besteht keine Notwendigkeit, die Zuordnung für die MASTER-Tabelle zu ändern, richtig? Dort hat sich doch nichts geändert, oder? Der Fehler FALSCH! Das Einfügen von Daten funktioniert mit diesem Mapping einwandfrei, aber die Auswahl von Daten führt zu schrecklichen SQLExcptions. Wenn Sie ein einfaches get auf ein Master-Objekt ausführen (wobei die Childs mit lazy="false" und fetch="join" konfiguriert sind, um den Fehler deutlicher zu machen), führt dies zu der folgenden Ausnahme:

org.hibernate.exception.SQLGrammarException: konnte eine Entität nicht laden: [com.xebia.hibernate.joinassociation.Master#0]
Verursacht durch: java.sql.SQLException: Spalte nicht gefunden: MASTER_FK in Anweisung [...]

Wenn Sie sich die ausgeführte SQL-Datei genauer ansehen, finden Sie die Ursache für die Ausnahme.

  Wählen Sie
  master0_.ID als ID0_2_,
  peer1_.ID as ID1_0_,
(1) peer1_1_.MASTER_FK als MASTER2_2_0_,
(3) Kinder2_.MASTER_FK als MASTER2_4_,
  Kinder2_.ID as ID4_,
  children2_.ID as ID3_1_,
(4) Kinder2_1_.MASTER_FK als MASTER2_4_1_
  von
  MASTER master0_
  linke äußere Verknüpfung
  PEER peer1_
(2) auf master0_.ID=peer1_.MASTER_FK
  linke äußere Verknüpfung
  PEER_JOIN peer1_1_
  on peer1_.ID=peer1_1_.ID
  linke äußere Verknüpfung
  KIND Kinder2_
(5) auf master0_.ID=children2_.MASTER_FK
  linke äußere Verknüpfung
  CHILD_JOIN Kinder2_1_
  on Kinder2_.ID=Kinder2_1_.ID
  wobei
  master0_.ID=?

Diese Aussage sieht wirklich merkwürdig aus:

  1. In der Select-Klausel wird die Spalte MASTER_FK korrekt aus der Tabelle PEER_JOIN abgefragt, ...
  2. aber in der Verknüpfungsbedingung wird aus der Tabelle PEER ausgewählt.
  3. In der Select-Klausel wird die Spalte MASTER_FK sowohl von der Tabelle CHILD
  4. als die Tabelle CHILD_JOIN,
  5. und in der Verknüpfungsbedingung wird es aus der Tabelle CHILD ausgewählt.

Nun befindet sich die Spalte MASTER_FK in beiden Fällen in der Tabelle ..._JOIN, weshalb sich die Datenbank über diese Abfrage beschwert. Für mich sieht das nach einem Fehler in Hibernate aus: In der Konfiguration wurde eindeutig angegeben, dass sich die Spalte MASTER_FK in der Tabelle ..._JOIN befindet. Bei einer Abfrage von der Master-Seite der Assoziation sollte Hibernate in der Lage sein, herauszufinden, wo diese Spalte zu finden ist. Eigentlich ist die Durchführung bidirektionaler Assoziationen mit Join-Tabellen in der Hibernate-Dokumentation sehr gut dokumentiert. Die für diese Konfiguration verwendete Syntax ist nur so kontraintuitiv, dass man sie sich nur schwer merken kann. Die korrekte Zuordnung lautet wie folgt:

  

(4)  

(5)  

(1)  

(2)  

(3)  

Die folgenden Änderungen wurden vorgenommen:

  1. Die Verknüpfung, die wir in der Konfiguration der Peer-Klasse hatten, wird auch in der Konfiguration der Master-Klasse hinzugefügt.
  2. Die Eins-zu-Eins-Zuordnung der Peer-Eigenschaft hat sich in eine eindeutige Viele-zu-Eins-Zuordnung geändert. Die Zuordnung für die Peer-Eigenschaft der Klasse Master ist jetzt ein Spiegelbild der Master-Eigenschaft in der Klasse Peer.
  3. Eine der Seiten der bidirektionalen Verknüpfungszuordnung muss invers sein. Darauf werde ich später zurückkommen.
  4. Die Kindermenge ist jetzt der Tabelle CHILD_JOIN zugeordnet...
  5. ...und es ist eine eindeutige Viele-zu-Viele-Zuordnung statt einer Eins-zu-Viele-Zuordnung geworden.

Wenn man sich die Tabellenstruktur für das bidirektionale Eins-zu-Eins auf einer Join-Tabelle ansieht, ist es verständlich, warum Hibernate sich für eine symmetrische Konfiguration entscheidet, denn die Tabellenstruktur (MASTER PEER) ist symmetrisch. Die SQL, die Hibernate aus diesem Mapping erzeugt, sieht wie folgt aus

  Wählen Sie
  master0_.ID als ID0_2_,
  master0_1_.ID als ID2_2_,
  Kinder1_.MASTER_FK als MASTER2_4_,
  child2_.ID as ID4_,
  child2_.ID as ID4_0_,
  child2_1_.MASTER_FK as MASTER2_1_0_,
  peer3_.ID as ID3_1_,
  peer3_1_.MASTER_FK als MASTER1_2_1_
  von
  MASTER master0_
  innere Verknüpfung
  PEER_JOIN master0_1_
  on master0_.ID=master0_1_.MASTER_FK
  linke äußere Verknüpfung
  CHILD_JOIN Kinder1_
  on master0_.ID=children1_.MASTER_FK
  linke äußere Verknüpfung
  KIND Kind2_
  on kinder1_.ID=kind2_.ID
  linke äußere Verknüpfung
  CHILD_JOIN kind2_1_
  on kind2_.ID=kind2_1_.ID
  linke äußere Verknüpfung
  PEER peer3_
  on master0_1_.ID=peer3_.ID
  linke äußere Verknüpfung
  PEER_JOIN peer3_1_
  on peer3_.ID=peer3_1_.ID
  wobei
  master0_.ID=?

Wie Sie sehen können, werden nun alle Spalten aus den richtigen Tabellen ausgewählt. Hibernate verknüpft ein paar Tabellen zu viel, aber das liegt daran, dass es jede Entität und Assoziation als separaten Teil der Abfrage ansprechen möchte. So wird dieSpalte CHILD_JOIN.MASTERFKeinmal als Teil der Assoziation Master->Child (mit dem Alias children1) und einmal als Eigenschaft von CHILD (mit dem Alias child21)abgefragt. Fazit Wir sind davon ausgegangen, dass wir die Konfiguration der Master-Klasse nicht ändern müssen, da wir die MASTER-Tabelle nicht geändert haben. Wir haben vergessen, dass die Mapping-Konfiguration auch alle Assoziationen enthält und dass sich diese Assoziationen geändert haben. Hibernate zwingt Sie dann, alle bidirektionalen Eins-zu-... Assoziationen in bidirektionale eindeutige Viele-zu-... Assoziationen zu ändern. Diese Konfiguration funktioniert gut, aber sie ist sehr kontraintuitiv und daher schwer zu merken und zu verwenden. Komplikation - Inversität Aus dieser Konfiguration ergibt sich eine Komplikation. Wie oben erläutert, müssen wir, wenn eine Verknüpfungstabelle bidirektional gemappt ist, die eine Seite der Assoziation invers machen. Dies sollte bei jeder bidirektionalen Assoziation immer gemacht werden. Die Zuordnung der Verknüpfung als invers bedeutet, dass die Eigenschaften in der Verknüpfung von dieser Seite der Assoziation nicht aktualisiert werden. Im obigen Beispiel haben wir die Verknüpfung in der Peer-Klassen-Zuordnung invers gemacht. Was nun, wenn wir der Klasse Peer eine Eigenschaft hinzufügen möchten, die der Tabelle PEER_JOIN zugeordnet ist? Normalerweise würden wir einfach eine <Eigenschaft hinzufügen ... /> Knoten in den <join .../> Knoten der Peer-Klassenkonfiguration einfügen, aber da die Verknüpfung auf dieser Seite invers ist, wird die Eigenschaft nicht gespeichert. Wir müssen also die Inversität dieser Assoziation auf die andere Seite verlagern. Wenn Sie nun die Verknüpfung in der Konfiguration der Master-Klasse invers machen, wird das Kaskadenverhalten unterbrochen. Bisher hatten wir Hibernate so konfiguriert, dass es den gesamten Objektbaum von einem Master-Objekt speichert. Die Einfügereihenfolge dafür war MASTER, PEER, PEER_JOIN, ... . Nachdem wir nun die Inversität auf die andere Seite der Verknüpfung verschoben haben, ändert hibernate die Reihenfolge in PEER, PEER_JOIN, MASTER, ... . Aber das wird nicht funktionieren, da zum Zeitpunkt des Einfügens der Zeile in PEER_JOIN der Wert für die nicht löschbare Spalte MASTER_FK noch nicht bekannt ist. Dies führt zu ConstraintViolationExceptions. Die einzige Möglichkeit, dieses Problem zu lösen, besteht darin, die Anwendung so zu ändern, dass der Objektbaum vom Peer-Objekt gespeichert wird. Das ist wirklich schlecht, denn es bedeutet, dass Sie Ihre Anwendung ändern müssen, damit Hibernate korrekt funktioniert. Ich kann mir nicht wirklich vorstellen, warum Hibernate nicht in der Lage ist, die richtige Reihenfolge für das kaskadische Speichern der Daten zu finden. Abschließend hoffe ich, dass dieser Blog Ihnen etwas Zeit bei der Suche nach dem richtigen Mapping für Ihre bidirektionale Assoziation mit Join-Tabelle erspart.

Verfasst von

Maarten Winkels

Contact

Let’s discuss how we can support your journey.