CodamAIDocs
Themafertig

Was beim Lesen in der Datenbank passiert

Die Feldauswahl steuert die SQL-Abfrage: nur die verlangten Spalten, Referenzen per LEFT JOIN, kein Nachladen im Hintergrund. Hier steht, warum das schnell ist und wann es teuer wird.

Ausprägungen
Tuple-ProjektionLEFT JOINzählen, IDs bestimmen, Felder holenausgebaute Einzelreferenz als eigenes LesenUnterlisten als eigene Abfragentiefe VerschachtelungUntertypen abstrakter Modelle

Worum es geht

Viele Frameworks laden erst ein ganzes Objekt und schneiden danach weg, was der Client nicht will. CDMS macht es umgekehrt: Die response wird vor der Datenbankabfrage ausgewertet, und die Abfrage liest nur die Spalten, die du angefordert hast.

Drei Begriffe, die auf dieser Seite vorkommen:

BegriffBedeutung
ProjektionDie Abfrage liest nur ausgewählte Spalten statt der ganzen Zeile. In CDMS heißt die Technik „Tuple-Projektion“: Das Ergebnis ist eine Liste von Werten statt eines vollständigen Objekts.
LEFT JOINVerbindet eine Tabelle mit einer zweiten, ohne Zeilen zu verlieren, bei denen es keine Verbindung gibt. Ein Mitarbeiter ohne Firma bleibt also im Ergebnis, sein Firmenfeld ist leer.
Nachladen (Lazy Loading)Ein Framework lädt eine Referenz heimlich nach, sobald der Code darauf zugreift. Das gibt es beim Lesen in CDMS nicht.

Von der response zur SQL-Abfrage

Links steht die response, rechts, was CDMS daraus macht (vereinfacht):

flowchart LR
    subgraph R["response"]
        direction TB
        r1["firstname"]
        r2["lastname"]
        r3["{ field: company, response: [companyname] }"]
    end
    subgraph S["SQL für employee"]
        direction TB
        s1["SELECT e.id, e._createdOn, e._updatedOn, Typ,<br/>e.firstname, e.lastname,<br/>c.id, Typ von c"]
        s2["FROM employee e<br/>LEFT JOIN company c ON e.company_id = c.id"]
        s3["WHERE e.id = '7f3…' UND Zeilenfilter"]
        s1 --> s2 --> s3
    end
    subgraph T["SQL für company"]
        direction TB
        t1["SELECT c.id, c._createdOn, c._updatedOn, Typ,<br/>c.companyname"]
        t2["FROM company c<br/>WHERE c.id = 'a1…' UND Zeilenfilter"]
        t1 --> t2
    end
    R ==> S
    S ==>|"company.id"| T

Drei Dinge siehst du daran:

  1. Nur verlangte Spalten. Aus employee liest CDMS firstname und lastname, dazu immer id, _createdOn, _updatedOn und den Typ. Andere Spalten fasst die Abfrage nicht an.
  2. Die Referenz kommt per LEFT JOIN, aber nur mit id und Typ. So weiß CDMS, auf welches Objekt company zeigt, ohne dessen Spalten zu lesen.
  3. Die ausgebaute Referenz ist ein eigenes Lesen. Mit der id liest CDMS die company über ihr eigenes Modell. Dabei gelten ihre Rechte, ihre Zeilenfilter und ihre Lese-Hooks, genau wie bei einem direkten read.

Die drei Schritte einer Abfrage

Jede Abfrage, auch das Lesen eines einzelnen Objekts, läuft in drei Schritten:

Wie CDMS eine Tabelle liest
  1. 1
    CDMS→Datenbank
    zählen: Wie viele Zeilen passen zum Filter?
    Das ergibt totalCount in meta. Sind es null Zeilen, ist CDMS hier schon fertig.
  2. 2
    CDMS→Datenbank
    IDs bestimmen: Welche Zeilen kommen auf diese Seite? Mit Filter, Sortierung, limit und page, aber nur mit den IDs
  3. 3
    CDMS→Datenbank
    Felder holen: Für genau diese IDs die angeforderten Spalten lesen, Referenzen per LEFT JOIN
  4. 4
    CDMS
    setzt die Werte zu Objekten zusammen, in der Reihenfolge aus Schritt 2

Die Werte kommen als flache Liste mit Namen wie company.id zurück. CDMS macht daraus wieder eine verschachtelte Struktur: { "company": { "id": … } }.

Wann es teuer wird

Eine einzelne Abfrage ist dank Projektion schnell. Teuer wird es durch die Anzahl der Abfragen. Jede ausgebaute Referenz und jede ausgebaute Liste ist eine eigene Abfrage, und zwar pro Objekt, das sie enthält.

Wie viele Abfragen entstehen?

Wann: POST /hr/employee/query mit limit: 25 und response: ["firstname", "lastname"]

Eine Abfrage auf employee, ohne JOIN.

Ergebnis: 1 Abfrage (in drei Schritten).

Wann: … und zusätzlich { "field": "company", "response": ["companyname"] }

Die employee-Abfrage bekommt einen LEFT JOIN für die id der Firma. Danach liest CDMS für jeden der 25 Mitarbeiter seine Firma.

Ergebnis: bis zu 1 + 25 Abfragen. Mitarbeiter ohne Firma brauchen kein zweites Lesen.

Wann: response: ["*"] auf employee mit den Referenzen company und department

* fordert jede Referenz mit ihrer id an. Auch das ist für jede Referenz ein eigenes Lesen, damit Rechte und Zeilenfilter des Zielmodells gelten.

Ergebnis: bis zu 1 + 25 × 2 Abfragen.

Wann: POST /hr/company/query mit limit: 25 und { "field": "employees", "response": ["firstname"] }

Für jede der 25 Firmen eine eigene Suche auf employee. Ohne limit in der Liste liefert jede Suche alle Mitarbeiter ihrer Firma.

Ergebnis: 1 + 25 Abfragen, bei 500 Mitarbeitern je Firma 12 500 Objekte.

Wann: … und in employees zusätzlich { "field": "department", "response": ["name"] }

Für jeden gelesenen Mitarbeiter kommt eine Abfrage für seine Abteilung dazu. Die Zahl multipliziert sich von Ebene zu Ebene.

Ergebnis: 1 + 25 + 25 × (Mitarbeiter je Firma) Abfragen.

Untertypen abstrakter Modelle

Liegt ein Feld nicht im Obermodell, sondern in einem Untertyp, verbindet CDMS die Tabelle des Untertyps über die gemeinsame id. Bei einer abstrakten Liste sucht CDMS erst auf dem Obermodell nur die IDs und Typen und liest dann je Untertyp alle seine Treffer mit einer Abfrage (id IN (…)). Siehe Lesen über abstrakte Typen.

Entscheidungstabelle

Was kostet ein Eintrag in der response?
EintragWirkung auf die Datenbank
einfaches Feld, z. B. "firstname"eine Spalte mehr in derselben Abfrage
"+"alle einfachen Spalten in derselben Abfrage
Einzelreferenz über * oder { field }ein LEFT JOIN, dazu ein Lesen pro Objekt
Liste über * oder { field }eine Suche pro Objekt, ohne limit mit allen Einträgen
Objekt in einem Objektmultipliziert sich mit der Ebene darüber

Faustregeln

Übersicht und Detail
Übersicht
POST /hr/employee/query
  • { "response": ["firstname", "lastname"], "parameter": { "limit": 25 } }
  • eine Abfrage, egal wie viele Referenzen das Modell hat
Detail
POST /hr/employee/read/{id}
  • { "response": ["+", { "field": "company", "response": ["companyname"] }] }
  • zusätzliche Abfragen nur für das eine geöffnete Objekt

Wie es weitergeht

Quellen im Code und in der Wissensdatenbank
  • CDMS/cdms-persistence-database – AbstractDatabasePersistence.queryObjects, SelectionBuilder, TupleMapper
  • CDMS/cdms-system-layer – AbstractLayer.recursiveRead, recursiveQuery, fetchAndSetModel, fetchAndSetList
  • documentation/20-api/03-response-requests.md
Suchen