Zum Inhalt springen

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.


@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 _MODELTYPE und muss eindeutig und stabil sein; eine Änderung entwertet bestehende Daten.
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 = true entsteht 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.

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.

OperationWoher der konkrete Typ kommt
createdata.@type im Payload
update (PUT)data.@type
update (PATCH)data["@type"]
read, deleteaus 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.

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.

AufgabeUmsetzung
konkreten Typ selektierenroot.type() als _MODELTYPE
Diskriminator ↔ KlassediscriminatorValue(...) / entityClassByDiscriminator(...)
Feld nur auf einem Untertyptreat-Ausdruck über treatedPath(...)
existiert das Attribut?hasAttribute(...) / declaresAttribute(...) über das JPA-Metamodell
  • Diskriminatorwerte sind stabil zu halten. Sie stehen in den Daten.
  • Bei abstrakten Reads gehört @type in 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.