Beziehungen und Rekursion
Kurzantwort: Jede Beziehung hat einen Typ (ONETOONE, ONETOMANY,
MANYTOONE, MANYTOMANY), ein Rückreferenz-Feld und eine Liste erlaubter
rekursiver Operationen (CREATE, READ, UPDATE, DELETE). CDMS pflegt beim
Schreiben immer beide Seiten und entscheidet anhand der Flags, ob ein
verschachteltes Objekt angelegt, geändert, nur referenziert, entkoppelt oder
gelöscht wird.
Die Beschreibung einer Beziehung
Abschnitt betitelt „Die Beschreibung einer Beziehung“@Reference(relation = RelationshipType.ONETOMANY, backReferenceFieldName = "customer", recursive = { RecursiveType.CREATE, RecursiveType.UPDATE, RecursiveType.DELETE })private Set<OrderEntity> orders;| Angabe | Bedeutung |
|---|---|
relation | Kardinalität und Besitzverhältnis |
backReferenceFieldName | Feld auf der Gegenseite, das zurückzeigt |
recursive | erlaubte rekursive Operationen über diese Beziehung |
Die Recursive-Flags
Abschnitt betitelt „Die Recursive-Flags“| Flag | Wirkung |
|---|---|
CREATE | Ein verschachteltes Objekt ohne id wird angelegt |
UPDATE | Ein verschachteltes Objekt mit id wird mitgeschrieben |
READ | Die Beziehung darf gelesen werden |
DELETE | Das Gegenüber ist ein abhängiges Kind: Entkoppeln bedeutet Löschen |
Ohne CREATE wird ein Eintrag ohne id im Strict Mode mit 400
(recursive-create-not-allowed|<feld>) abgelehnt, sonst still verworfen. Ohne
UPDATE wird ein Eintrag mit id nur referenziert, sein Inhalt bleibt
unangetastet.
Entscheidungsbaum beim Schreiben
Abschnitt betitelt „Entscheidungsbaum beim Schreiben“flowchart TB
A["verschachteltes Objekt im Payload"] --> B{"id vorhanden?"}
B -->|nein| C{"RecursiveType.CREATE?"}
C -->|ja| D["recursiveCreate<br/>+ Rückreferenz setzen"]
C -->|"nein, Strict Mode"| E["400 recursive-create-not-allowed"]
C -->|"nein, tolerant"| F["Eintrag verwerfen"]
B -->|ja| G{"RecursiveType.UPDATE?"}
G -->|ja| H["recursiveUpdate / recursivePatch<br/>der bestehenden Zeile"]
G -->|nein| I["nur referenzieren"]
Beide Seiten werden gepflegt
Abschnitt betitelt „Beide Seiten werden gepflegt“setReference(...) im System-Layer setzt je nach Beziehungstyp beide Enden:
| Typ | Aktion |
|---|---|
ONETOONE | Feld setzen und Rückreferenz auf der Gegenseite setzen |
ONETOMANY | Element in die Sammlung aufnehmen und Rückreferenz setzen |
MANYTOONE | Feld setzen und in die Sammlung der Gegenseite aufnehmen |
MANYTOMANY | beide Sammlungen pflegen |
Beim Entkoppeln räumt removeBackReference(...) symmetrisch wieder auf. Fehlende
Sammlungen werden dabei als LinkedHashSet angelegt – dadurch bleibt die
Reihenfolge stabil und die Mengen-Semantik erhalten.
Der Grund für den Aufwand: Hibernate persistiert nur, was über die besitzende Seite und Dirty Checking erkannt wird. Ein im Debugger korrekt aussehendes Objekt genügt nicht.
Die Rückreferenz beim rekursiven Schreiben
Abschnitt betitelt „Die Rückreferenz beim rekursiven Schreiben“Schreibt CDMS rekursiv in ein Kind, wird das Feld, das auf den auslösenden
Elternteil zeigt, übersprungen (excludeField). Die Zugehörigkeit verwaltet
allein der Parent – andernfalls würde das Kind die gerade hergestellte
Verknüpfung wieder auflösen.
Listen als Zielzustand
Abschnitt betitelt „Listen als Zielzustand“Eine gesendete Liste beschreibt den Zielzustand der Beziehung. Alles, was
nicht in ihr steht, wird über reduceToTargetState(...) entfernt:
- ohne
DELETE-Flag: Mitglied bleibt bestehen, Rückreferenz wird geleert, das Mitglied wird gespeichert - mit
DELETE-Flag: Mitglied wird rekursiv gelöscht
Bei PUT bedeutet eine fehlende Liste „leer”. Bei PATCH bedeutet eine fehlende
Liste „unangetastet”, ein null oder [] dagegen „leeren”. Siehe
Schreibsemantik.
Many-to-Many über Join-Entity
Abschnitt betitelt „Many-to-Many über Join-Entity“Für Beziehungen mit eigenen Attributen wird eine explizite Join-Entity
bevorzugt (z. B. User, Group, User2Group). Vorteile: zusätzliche Felder,
eigene Auditierung, klare Persistenzkontrolle.
Zu beachten:
- die Join-Entity muss auf beiden Seiten korrekt referenziert sein
- die besitzende Seite bestimmt den Fremdschlüssel
- Cascade-Regeln bewusst setzen
- bei NOT-NULL-Fremdschlüsseln muss die Referenz der besitzenden Seite vor dem Flush stehen
public void addGroup(GroupEntity group) { User2GroupEntity link = new User2GroupEntity(); link.setUser(this); link.setGroup(group); this.groups.add(link);}Lesen von Beziehungen
Abschnitt betitelt „Lesen von Beziehungen“Beziehungen werden nur geladen, wenn der Response Request sie anfordert:
- Einzelreferenz:
recursiveReadüber den System-Layer des Zieltyps - Listenreferenz: eigene Abfrage mit automatisch ergänztem Filter
<backReference>.id = <id des Elternobjekts>, plus dem imparameterangegebenen Filter, Sortierung und Pagination - Beziehungen auf abstrakte Typen laufen über die Untertyp-Auflösung, siehe Vererbung
In der Datenbank werden Beziehungen mit LEFT JOIN angebunden, damit Objekte
ohne Beziehung nicht aus dem Ergebnis fallen. Ein impliziter Zugriff über einen
Pfad wie root.get("model").get("id") erzeugt dagegen einen Inner Join und
würde genau das tun – deshalb wird explizit gejoint.
Zyklusschutz
Abschnitt betitelt „Zyklusschutz“Jedes DTO trägt ein Feld _reference. Beim Betreten der Rekursion setzt CDMS
dort eine UUID; ein bereits besuchtes Objekt wird nicht erneut verarbeitet.
Ohne diesen Schutz würde ein bidirektionaler Graph endlos laufen.