Vererbung und abstrakte Modelle
Kurzantwort: Ein abstraktes Modell ist nicht instanziierbar und kann deshalb
nicht direkt abgefragt werden. CDMS löst es in zwei Schritten auf: erst nur
id + konkreter Typ über die abstrakte Wurzel, dann die vollen Objekte je
Untertyp über dessen eigenen System-Layer mit id IN (…). Der konkrete Typ
reist als @type (JSON) bzw. _MODELTYPE (Datenbank) mit.
JPA-Abbildung
Abschnitt betitelt „JPA-Abbildung“@Entity(name = "tenant.contact")@Table(name = "tenant-contact")@Inheritance(strategy = InheritanceType.JOINED)@DiscriminatorColumn(name = "_MODELTYPE", length = 255)@DiscriminatorValue("tenant.contact")public class ContactEntity extends AbstractPersonEntity { }- Strategie ist
JOINED– jede Ebene bekommt eine eigene Tabelle. - Der Diskriminator steht in
_MODELTYPEund muss eindeutig und stabil sein; eine Änderung entwertet bestehende Daten.
Der Zwei-Stufen-Read
Abschnitt betitelt „Der Zwei-Stufen-Read“sequenceDiagram
participant C as Client
participant H as {Abstract}ApiHub
participant P as Persistenz
participant S1 as System-Layer Untertyp A
participant S2 as System-Layer Untertyp B
C->>H: POST /query (Filter, Sortierung, Seite)
H->>P: queryAbstractInfo – nur id + _MODELTYPE
P-->>H: [(id1, typA), (id2, typB), (id3, typA)]
Note over H: nach Typ gruppieren
par parallel (virtuelle Threads)
H->>S1: id IN (id1, id3) + Response Request
H->>S2: id IN (id2) + Response Request
end
S1-->>H: volle Objekte
S2-->>H: volle Objekte
H->>H: Originalreihenfolge wiederherstellen,<br/>jedes Objekt mit @type versehen
H-->>C: QueryResponse
Warum der Umweg? Die abstrakte Wurzel lässt sich nicht instanziieren, und nur die konkreten Untertypen haben eigene System-Layer-Beans – nur über sie greifen deren eigene Filter (Mandant, Owner, Attributfilter) und die Feldprojektion.
Wichtige Eigenschaften:
- Filter, Sortierung und Pagination wirken auf der Meta-Abfrage; die
Zweitabfrage je Untertyp läuft ohne Limit (
limit = -1) über die schon ausgewählten IDs. - Die Reihenfolge der Meta-Abfrage wird beim Zusammensetzen wiederhergestellt.
- Die Gesamtzahl bei
meta = trueentsteht als Summe der gefilterten Counts je konkretem Untertyp, damit die Filter jedes Untertyps zählen. - Untertypen können selbst abstrakt sein; gezählt und abgefragt werden nur die konkreten Blätter.
Parallelität
Abschnitt betitelt „Parallelität“Die Hub-API lädt die Zeilen mit virtuellen Threads
(cdms_hub_query_parallelism, Standard 8). Jeder Worker bekommt den vollen
Request-Kontext propagiert (Spring-Request-Attribute und
RequestContextHolder) und gibt seine Verbindungen am Ende zurück.
Ausnahme: Ist im aufrufenden Thread bereits eine Schreibtransaktion offen, wären deren noch nicht committete Zeilen für die Worker unsichtbar. In diesem Fall liest der Hub seriell im aufrufenden Thread.
Schreiben auf abstrakten Modellen
Abschnitt betitelt „Schreiben auf abstrakten Modellen“| Operation | Woher der konkrete Typ kommt |
|---|---|
create | data.@type im Payload |
update (PUT) | data.@type |
update (PATCH) | data["@type"] |
read, delete | aus der Datenbank ermittelt (_MODELTYPE) |
Beim rekursiven Anlegen einer abstrakten Beziehung im PATCH ist @type
Pflicht – ohne ihn ließe sich keine Instanz bauen; der Fehler lautet
missing-type-for-abstract-field|<feld> (400). Beim Lesen über die id ist er
optional, weil die Auflösung polymorph über die deklarierte Klasse läuft.
Verschachtelte Listen abstrakten Typs
Abschnitt betitelt „Verschachtelte Listen abstrakten Typs“Eine Listenreferenz auf ein abstraktes Modell nimmt denselben Weg wie eine
Top-Level-Abfrage: Meta-Abfrage, Gruppierung, id IN (…) je Untertyp. Der
Response Request wird dabei für jeden konkreten Untertyp expandiert und die
Feldmengen vereinigt – ein + liefert also die skalaren Felder aller möglichen
Untertypen.
Auf Datenbankebene
Abschnitt betitelt „Auf Datenbankebene“| Aufgabe | Umsetzung |
|---|---|
| konkreten Typ selektieren | root.type() als _MODELTYPE |
| Diskriminator ↔ Klasse | discriminatorValue(...) / entityClassByDiscriminator(...) |
| Feld nur auf einem Untertyp | treat-Ausdruck über treatedPath(...) |
| existiert das Attribut? | hasAttribute(...) / declaresAttribute(...) über das JPA-Metamodell |
Regeln aus der Praxis
Abschnitt betitelt „Regeln aus der Praxis“- Diskriminatorwerte sind stabil zu halten. Sie stehen in den Daten.
- Bei abstrakten Reads gehört
@typein die Antwort, sonst kann der Client die Untertypen nicht unterscheiden. - Vererbungsziel ist immer ein AbstractModel – so die verbindliche Modellierungsregel.
- Optionale Beziehungen brauchen
LEFT JOIN. Ein impliziter Inner Join über einen Pfadzugriff schließt Datensätze ohne Beziehung aus.