CodeGym /Kurse /Hibernate deep-dive /Dimensionen des finalen Persistence-Audits

Dimensionen des finalen Persistence-Audits

Hibernate deep-dive
Level 30, Lektion 0
Verfügbar

1. Persistence-Audit: Prüfungsrahmen

Das endgültige Persistence-Audit ist kein „Nehmen Sie eine Lupe und finden Sie, wo Sie fetch=LAZY vergessen haben“. Es gleicht eher einer technischen Inspektion eines Autos: Wir diskutieren nicht, ob uns die Lackfarbe gefällt (Code-Stil), sondern prüfen, wo das Benzin wirklich hingeht (SQL), ob die Bremsen halten (Nebenläufigkeit) und ob das Rad in der Kurve nicht abfällt (Korrektheit). Wir beginnen immer mit dem Anwendungsfall.

An diesem Punkt haben Sie bereits einzeln flush, lazy loading, merge, locking, bulk und fetching behandelt. Das endgültige Audit dient genau dazu, diese nicht mehr als isolierte Themen zu betrachten: Zuerst wählen wir den Hot Path und die Risikoachse, dann suchen wir Red Flags, anschließend prüfen wir Verdächtige anhand von SQL und Tests, und erst danach entscheiden wir, was zuerst repariert werden soll.

Der entscheidende Perspektivwechsel hier ist: Wir prüfen das Verhalten des Systems in einem konkreten Szenario, nicht die „Qualität der Entitäten im Vakuum“. Selbst eine ideale @Entity kann zur Problemquelle werden, wenn sie als universelles Read-Model verwendet, durch Schichten gezogen und versehentlich in Streams, Logs oder Serialisierung navigiert wird. Und umgekehrt: Manchmal ist ein „nicht schönster“ Codeabschnitt eines Repositories völlig in Ordnung, weil er exakt einen Hot Path bedient und durch einen Test abgesichert ist.

Damit das Audit nicht zu einem chaotischen „Ich habe die Repositories überflogen“ verkommt, führen wir Audit-Dimensionen ein – separate Prüfungsaspekte. In unserem Kurs gibt es vier davon, die gezielt mit dem übereinstimmen, was wir den ganzen Monat gelernt haben:

Audit-Dimension Was prüfen wir im Kern Worauf achten wir im Code/SQL Beispiel aus Commerce Persistence Lab
Correctness Das System liest und ändert Daten vorhersagbar und korrekt Zustandsübergänge, Flush-Timing, merge, Bulk-Nebeneffekte, Sichtbarkeit von Soft Deletes „Produkt umbenennen“, „Bulk-Löschen“ und anschließendes Lesen in derselben Transaktion
Performance Das System führt eine vernünftige Anzahl von SQL-Befehlen aus und zieht keine überflüssigen Daten Abfrageanzahl, N+1, Breite des SELECT, Join-Form, Batching/Bulk vs. Schleife Bestell‑/Produktliste, Bestelldetails, Import/Massenaktualisierung
Concurrency Bei parallelen Schreibvorgängen gehen keine Daten verloren und werden nicht beschädigt @Version, Lock-Modi, Transaktionsgrenzen, Länge kritischer Abschnitte Reservierung von Beständen für InventoryItem
Mapping quality Beziehungsmodell und Mapping erzeugen keine „Zeitbomben“ zur Laufzeit Owning Side, Cascade, Orphan Removal, Equality, ID-Strategie, Constraints/Indizes PurchaseOrder ↔ OrderItem, Link-Entity ProductCategoryAssignment

Ein weiteres wichtiges Wort, das heute immer wieder auftauchen wird, ist Hot Path. Dies ist das wichtigste/häufigste/teuerste Szenario, mit dem man beginnen sollte. Wenn man versucht, „alles auf einmal zu auditieren“, endet man in dem Zustand: „Ich habe 200 Methoden geprüft und verstehe nicht, was wichtig war.“ Ein Audit geht immer um Prioritäten und Nachweise, nicht um Heldentum.

2. Correctness: Vorhersagbare Datenänderungen

Correctness ist die „unromantischste“ Dimension: Sie geht nicht um Geschwindigkeit oder Schönheit. Sie geht darum, dass die Daten in der Datenbank Ihren Erwartungen entsprechen und die Erwartungen den Geschäftsregeln. Anfänger scheitern hier oft, weil ORM vieles „von selbst“ erledigt und es so aussieht, als wäre alles unter Kontrolle … bis sich herausstellt, dass ein Bulk-Update veraltete Objekte im Persistence Context hinterlassen hat.

Die einfachste Frage des Correctness-Audits klingt langweilig, rettet aber Projekte: „Was gilt als korrektes Ergebnis dieses Anwendungsfalls?“. Zum Beispiel bedeutet „Produkt umbenennen“, dass am Ende der Transaktion tatsächlich ein UPDATE product set name = ... where id = ... ausgeführt wurde und nichts Überflüssiges passiert ist. „Produkt löschen“ in einem Soft-Delete-Modell bedeutet kein DELETE, sondern eine Änderung des Flags/Zustands und das korrekte Verstecken aus Lesevorgängen. „Bestellung aktualisieren“ bedeutet, dass wir keine Positionen verlieren, keine Duplikate erzeugen und kein unerwartetes kaskadierendes DELETE durch ein ungeschicktes orphanRemoval erhalten.

Schauen wir uns das grundlegendste „Correctness-Stück“ an, das Sie bereits erklären können: Eine Managed Entity wird ohne save() geändert.

import org.springframework.transaction.annotation.Transactional;

@Transactional // Wichtig: Ohne Transaktion können Änderungen möglicherweise nicht bis zum Flush/Commit überleben, wie Sie erwarten
public void renameProduct(Long id, String newName) {
    // Managed-Entität laden: Änderungen werden dann vom Persistence Context verfolgt
    Product product = productRepository.findById(id).orElseThrow();

    // Zustand im Speicher ändern: SQL UPDATE erscheint später (bei Flush/Commit)
    product.setName(newName);
}

Aus Sicht des Correctness-Audits prüfen wir hier nicht, ob uns die Methode gefällt. Wir prüfen, ob dies tatsächlich ein Managed-Update-Flow ist, ob es eine Transaktion gibt und ob das UPDATE genau einmal und zum richtigen Zeitpunkt erscheint. Wenn jemand daneben „für alle Fälle“ ein saveAndFlush() eingefügt hat, muss das Audit die Frage aufwerfen: Warum ein erzwungener Flush? Wird er für die Korrektheit benötigt (z. B. damit die Daten vor der nächsten Abfrage in derselben Transaktion garantiert in der DB sind) oder ist es nur eine Gewohnheit, die Rauschen erzeugt?

Im Correctness-Bereich taucht schnell ein zweiter „Klassiker“ auf – Bulk-Operationen und der veraltete Persistence Context. Bulk wird fast immer aus Performance-Gründen verwendet, aber das Correctness-Audit muss fragen: Was passiert mit bereits geladenen Entitäten?

import org.springframework.transaction.annotation.Transactional;

@Transactional
public ProductStatus staleAfterBulk(Long id) {
    // 1) Entität in den Persistence Context laden – jetzt ist es ein Managed-Objekt
    Product product = entityManager.find(Product.class, id);

    // 2) Bulk-UPDATE ausführen: wird direkt in der DB ausgeführt und synchronisiert NICHT die bereits geladenen Managed-Entitäten
    productRepository.markDeleted(id);

    // 3) Feld aus dem Managed-Objekt lesen: wir erhalten den alten Wert (veralteter Zustand)
    //    Übliche Korrekturen: clear()/refresh(), separate Transaktion, erneutes Lesen usw.
    return product.getStatus();
}

Dies ist ein Beispiel, bei dem Correctness und Performance buchstäblich Hand in Hand gehen und gleichzeitig versuchen, Sie ins Gesicht fallen zu lassen. Das Bulk-Update hat die Zeile in der DB geändert, aber das Managed-Objekt product im Persistence Context ist im alten Zustand geblieben. Wenn der Code danach weiterhin Entscheidungen auf Basis von product trifft, erhalten Sie ein logisch inkorrektes Verhalten. Korrekturen haben wir bereits im Bulk-Modul besprochen: clear(), refresh(), Auslagerung des Bulk in eine separate Transaktion, erneutes Lesen. Im Audit ist es nicht wichtig, „das richtige Mittel zu erraten“, sondern das Risiko selbst zu erkennen und als Finding zu dokumentieren.

Das Correctness-Audit stellt auch ständig Fragen zu merge(). Wenn Sie sehen, dass ein Service ein DTO entgegennimmt, eine detachierte Entität baut und entityManager.merge(dtoAsEntity) aufruft, müssen Sie innehalten und fragen: Ist das für Ihren Entity-Graphen wirklich sicher? Erzeugt es unnötige SELECT-, zieht es unerwartete Child-Objekte mit, löscht es etwas durch Orphan Removal? Im Kurs sind wir immer wieder zu dem Schluss gekommen, dass für die meisten Geschäftsszenarien der find + mutate-Stil vorhersagbarer ist. Im Audit wird dies nicht zum Dogma, sondern zur Frage: Warum wurde hier der Merge-Flow gewählt und wie ist seine Korrektheit nachgewiesen?

Und schließlich taucht Correctness gerne dort auf, wo man es nicht erwartet: an der Grenze zwischen Transaktion und Lazy Loading. Selbst wenn „alles lokal funktioniert hat“, muss das Audit die Kursbasis im Kopf behalten: open-in-view=false. Wenn die Datenschicht eine Entität nach außen gibt und die äußere Schicht nach der Transaktion auf Lazy-Beziehungen zugreift, handelt es sich um einen Correctness-Bug, der als „einfache Ausnahme“ getarnt ist.

3. Performance: SQL zählen und den Preis sehen

Ein Performance-Audit beginnt nicht mit „es läuft langsam“, sondern mit einer einfachen Ingenieursgewohnheit: Zählen, wie viele und welche Abfragen für einen einzigen Anwendungsfall ausgeführt werden. ORM-Projekte sterben oft nicht an „komplexem SQL“, sondern an „zu vielen einfachen SQLs“ – Tausende kleiner SELECT, die einzeln harmlos sind, aber zusammen eine Mikrowelle für Ihre CPU ergeben.

Das Schöne am Performance-Audit ist, dass es leicht evidenzbasiert wird: Sie aktivieren sql-trace/stats, führen das Szenario aus – und alles wird sichtbar. Sie müssen nicht mit Chakren raten, sondern schauen einfach ins Log. Wichtig ist jedoch die Disziplin: Wir optimieren nicht „die gesamte Anwendung“, sondern ein konkretes Szenario. Die Produktliste im Admin, die Bestelldetails, der Import von Snapshot-Daten – das sind unterschiedliche Geschichten mit unterschiedlichen Preisen.

Ein klassischer Red Flag für Performance ist: „Zuerst alle Entitäten holen, dann in Java etwas daraus extrahieren.“ Es sieht harmlos aus, löst aber oft N+1 (oder zumindest Overfetching) aus.

import java.util.List;

public List<String> findOrderEmails() {
    // Potenzielles Problem: findAll() lädt Bestellungen, dann „wandern“ wir durch Beziehungen
    // Wenn customer LAZY ist (was normalerweise der Fall ist), kann jede Iteration einen separaten SELECT auslösen
    return orderRepository.findAll().stream()
            .map(order -> order.getCustomer().getEmail()) // potenzielles N+1 auf customer
            .toList();
}

Im Performance-Audit müssen Sie die Methode nicht sofort umschreiben. Sie müssen Symptom und Nachweis dokumentieren: Wie viele Abfragen wurden ausgeführt, in welcher Form, wo gab es sekundäre Selects? Und dann wählen Sie eine Lösung aus der Matrix: Projection, EntityGraph, JOIN FETCH, Batch Fetching oder einen anderen Ansatz. Hier geht es darum, die Art des Problems zu erkennen: „Versteckte Navigation durch den Entity-Graphen in einem Stream“ ist fast immer eine Einladung zur N+1-Party, und Hibernate bringt dazu nicht Pizza, sondern Rechnungen für die Infrastruktur.

Das Performance-Audit betrachtet nicht nur Lese-, sondern auch Schreiboperationen. Dort gibt es besondere Lieblinge:

Wenn Sie eine „Massenoperation“ als Schleife über Entitäten mit save() in jeder Iteration sehen, stellt das Audit die Frage: Ist Batching aktiviert? Passt die ID-Strategie? Gibt es flush()/clear() in der Schleife? Wird der Persistence Context nicht zur Größe eines Kleinstaats aufgebläht? Und wenn Sie eine Massenänderung sehen (z. B. „Katalog neu bewerten“), fragt das Audit: Muss das wirklich als Entity-Loop und nicht als Bulk-Update erfolgen? Und wenn es ein Bulk-Update ist – kehren Sie zur Correctness zurück und prüfen Sie den veralteten Zustand. Das ist ein normaler Kreislauf, so soll es sein.

Ein weiterer wichtiger Performance-Marker ist die Breite des Ergebnisses. Manchmal gibt es „nur eine“ Abfrage, aber sie schleppt 25 Spalten und fünf Joins für eine Liste, die nur drei Felder benötigt. Solche Dinge tarnen sich perfekt hinter dem Satz „dafür kein N+1“. Im Audit-Kopf sollte ein einfacher Gedanke leben: Eine Abfrage kann schlechter sein als zehn, wenn sie aufgebläht ist und einen riesigen Entity-Graphen materialisiert.

4. Concurrency: Paralleles Schreiben

Ein Concurrency-Audit kommt fast immer „zu spät“ ins Projekt: wenn Daten bereits verloren sind und das Unternehmen bereits gefragt hat: „Warum haben wir minus drei iPhones im Lager?“ Die gute Nachricht: In unserem Übungsprojekt gibt es einen ehrlichen umkämpften Write-Path: InventoryItem und die Bestandsreservierung. Das ist ein ideales Übungsobjekt: Parallele Aktualisierungen sind hier keine Abstraktion, sondern eine normale Situation.

Die erste Frage des Concurrency-Audits klingt grob, ist aber ehrlich: Haben wir einen Schutz vor Lost Update? Wenn nicht, hängt die „Korrektheit“ des Systems davon ab, wie stark die Benutzer zeitlich übereinstimmen. Spoiler: In der Produktion tun sie das.

Das grundlegende Werkzeug ist das optimistische Sperren mit @Version. Und im Audit sieht das maximal einfach aus: Eine umkämpfte Entität hat eine Version oder nicht.

import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Version;

@Entity
public class InventoryItem {
    @Id
    private Long id;

    @Version
    private long version; // Version wird benötigt, um Lost Updates bei konkurrierenden Änderungen abzufangen

    private int availableQty; // Domänenfeld: verfügbarer Bestand, um den es normalerweise bei umkämpften Schreibvorgängen geht
}

Weiter prüft das Audit, dass die Schreiboperationen tatsächlich in einer vernünftigen Transaktion leben (kurz und klar) und dass ein Konflikt als erwartetes Ereignis behandelt wird, nicht als „oh, 500“. Wir gehen hier nicht in das API-Design, aber auf der Datenschichtebene ist es wichtig, dass der Service entweder einen erneuten Versuch durchführen kann (falls zulässig) oder den Konflikt ehrlich nach oben weitergibt.

Wenn optimistisches Sperren nicht geeignet ist (hohe Konkurrenz, teure Wiederholungen), kommt pessimistisches Sperren ins Spiel. Und dann hört das Audit auf, „nur eine Annotation“ zu sein, und wird zur „Architekturfrage“: Wie kurz ist der kritische Abschnitt, gibt es ein Timeout, halten wir den Lock über eine lange Geschäftslogik? In diesem Kurs haben wir bereits die Regel festgehalten: Ein DB-Lock darf nicht über Benutzerinteraktionen und lange Schritte hinweg gehalten werden. Das Concurrency-Audit prüft einfach, ob das Team nach dieser Regel lebt und nicht nur in der Vorlesung nickt.

Eine besondere Feinheit: Concurrency-Probleme sind in normalen Unit-Tests fast nie sichtbar, und selbst Integrationstests müssen den Konflikt deterministisch reproduzieren. Sonst prüfen Sie nicht den Schutz vor Lost Updates, sondern spielen Lotto.

5. Mapping-Qualität: Qualität des Mappings

Die Mapping-Qualität wird meist unterschätzt, weil das Mapping „statisch“ aussieht: Einmal Annotationen geschrieben – und vergessen. Aber in der Hibernate-Welt ist es grausam: Was Sie im Mapping festlegen, bestimmt die Form des SQL, die Kaskaden, die Flush-Reihenfolge, das Verhalten von Collections und sogar, wie sicher es ist, Entitäten zu loggen. Daher muss das Audit nicht nur „Abfragen“ prüfen, sondern auch „den Boden, auf dem die Abfragen wachsen“.

Der praktischste Ansatz im Mapping-Audit ist zu fragen: Welche Lebenszyklusregeln drückt dieses Mapping aus? Wenn Sie eine bidirektionale Beziehung haben, hängt die Korrektheit von der Synchronisation beider Seiten ab. Wenn Sie eine Cascade haben, sollte sie Ownership ausdrücken, nicht „vorsichtshalber ALL“. Wenn Sie orphanRemoval haben, müssen Sie sicher sein, dass das Trennen der Beziehung tatsächlich das Löschen bedeutet.

Ein Minibeispiel, das „zu einfach“ erscheint, aber regelmäßig Projekte zum Scheitern bringt: die Helper-Methode auf einem Bestell-Aggregat.

public void addItem(OrderItem item) {
    // Zur Collection auf der Aggregatseite hinzufügen (normalerweise Inverse Side)
    items.add(item);

    // Zwingend die Owning Side synchronisieren, sonst können FK/Flush-Reihenfolge „seltsam“ sein
    item.setOrder(this);
}

In der Audit-Logik freuen wir uns nicht einfach, dass die Methode existiert. Wir prüfen, ob der Code sie auch tatsächlich verwendet, ob es kein direktes order.getItems().add(item) in den Services gibt und ob die Flush-Reihenfolge und der FK nicht zu unerwarteten UPDATE/NULL auf dem Fremdschlüssel führen. Mapping ist keine Dekoration, sondern ein Vertrag.

Zur Mapping-Qualität gehören auch „subtilere“ Dinge. Equality/hashCode() mit Proxy beeinflusst Set und Orphan Removal; toString() kann Lazy Loading auslösen; die ID-Strategie beeinflusst Batching; Constraints und Indizes bestimmen, wie gut Ihr Schema Invarianten schützt (sku ist eindeutig, orderNumber ist eindeutig, das Paar product+category ist eindeutig). All das ist Teil des Mapping-Audits, auch wenn es nach „DBA-Thema“ aussieht. In unserem Kurs haben wir das bewusst als Teil des Modells behandelt, nicht als „fügen wir später hinzu“.

6. Zusammenhang der Dimensionen und Priorisierung

Ein Problem – mehrere Dimensionen: Symptome nicht verwechseln

Der erwachsene Teil des Audits beginnt dort, wo Sie aufhören, Probleme in hübsche Schachteln zu sortieren. In der Realität trifft derselbe Fehler oft mehrere Dimensionen gleichzeitig. Und wenn Sie ihn nur „an einer Stelle“ behandeln, kommt er in anderer Form zurück, wie ein Bumerang … nur ist der Bumerang dann auch noch beleidigt.

Zum Beispiel äußert sich ein schlechter Fetch-Plan normalerweise als Performance-Schmerz (N+1, unnötige sekundäre Selects), verwandelt sich aber sehr leicht in einen Correctness-Schmerz an der Transaktionsgrenze: „Im Service nicht nachgeladen, von außen versucht zu lesen, LazyInitializationException erhalten.“ Genauso beginnt ein Bulk-Update als Performance-Lösung („ein UPDATE statt tausend“), erzeugt aber ein Correctness-Risiko durch veralteten Zustand im Persistence Context. Und das Fehlen von @Version sieht aus wie ein Concurrency-Problem, aber das Ergebnis sind inkorrekte Daten (also Correctness).

Um die Übersicht zu behalten, hilft es, eine kurze „Verbindungsliste“ im Kopf zu haben:

Situation Womit wird sie oft verwechselt Welche Dimensionen sind tatsächlich betroffen
N+1 in einer Liste „muss EAGER setzen“ performance + (oft) mapping quality, manchmal correctness (Lazy-Boundary)
Bulk-Update/-Delete „einfach schnelles SQL“ performance + correctness (veralteter Zustand)
Kein @Version „naja, wir schreiben selten parallel“ concurrency + correctness
Falsche Owning Side „irgendwie aktualisiert Hibernate seltsam“ mapping quality + correctness (und manchmal performance durch unnötige UPDATE)
saveAndFlush() „überall“ „so ist es zuverlässiger“ correctness (Flush-Timing) + performance (unnötiger Flush und SQL)

Der Sinn der Tabelle ist nicht, sie wie ein Gebet auswendig zu lernen. Der Sinn ist, bei jedem Finding zwei Fragen zu stellen: „Welches Symptom hat welche Dimension gezeigt?“ und „Welche Dimensionen sind tatsächlich betroffen?“ Das erhöht die Qualität der Entscheidungen erheblich und verringert die Wahrscheinlichkeit, „eins zu reparieren und ein anderes zu zerbrechen“.

Prüfungsstart: Hot Path und Prioritäten

Die größte methodische Falle am finalen Tag ist der Versuch, ein Held zu sein und alles auf einmal zu prüfen. Das endet damit, dass Sie 30 Anmerkungen finden, müde werden, und die gefährlichste Stelle (z. B. die konkurrierende Bestandsbuchung) bleibt unbeachtet. Deshalb beginnt das Audit nicht mit dem Durchgehen des Codes, sondern mit der Auswahl des Hot Path – des Szenarios, bei dem ein Fehler am teuersten ist.

Ein Hot Path ist nicht unbedingt das „schönste“ Szenario. Es ist dasjenige, das entweder häufig ausgeführt wird, datenkritisch ist oder infrastrukturmäßig sehr teuer ist. In unserem Commerce Persistence Lab sind typische Kandidaten alltäglich: die Produktliste im Admin (häufiges Lesen), die Bestelldetails mit Positionen (komplexes Lesen), die Bestandsreservierung (konkurrierendes Schreiben), die Massenpreisänderung (Massen-Schreiben), das Lesen des Änderungsverlaufs (Audit als separater Pfad).

Danach suchen wir Red Flags – Anzeichen potenzieller Probleme noch vor einer genauen Messung. Sie beweisen keinen Fehler, sagen aber „hier musst du dir das SQL ansehen“. Beispiele für Red Flags haben Sie bereits hunderte Male gesehen, nur haben Sie sie bisher nicht so genannt: findAll() und danach Durchlaufen des Graphen; EAGER standardmäßig; saveAndFlush() in einem Service ohne Erklärung; Bulk-Abfrage und danach Arbeit mit bereits geladenen Entitäten; fehlendes @Version auf einer umkämpften Entität; „direkte“ Änderungen an Collections ohne Helper-Methoden.

Die Priorisierung von Findings beginnt in einem guten Team normalerweise mit einer einfachen Regel: Correctness und Concurrency auf dem Hot Path haben die höchste Priorität. Dann kommen teure Performance-Fehler auf häufigen Read-Pfaden. Und erst danach kommt die „Kosmetik“ (obwohl Kosmetik auch nützlich sein kann – man darf nur nicht die Datensicherheit damit ersetzen). Red Flags allein beweisen noch nichts. Um sie nicht aus Gewohnheit zu behandeln, braucht es eine klare Entscheidungsmatrix: Wann ist eine Projection angebracht, wann eine Entity mit Fetch-Plan, wann Bulk, wann natives SQL. Ein Audit ist Ordnung, nicht Umfang.

7. ReviewFinding: Ergebnis des Audits

Wenn Sie die Ergebnisse des Audits nicht in einem wiederholbaren Format festhalten, wird das Audit zu einem Gespräch: „Mir scheint, hier stimmt etwas nicht.“ Aber „scheint“ ist ein wunderbares Wort für Poesie, aber ein sehr gefährliches Wort für die Datenschicht. Daher führt die letzte Stufe eine Disziplin ein: Jede Erkenntnis wird als Finding formuliert, mit getrennter Dokumentation von Symptom, Ursache, Nachweis, Lösung und Priorität.

Das Format kann in Confluence, Markdown oder Jira sein – aber das Denken ist einfacher mit einer minimalen Struktur. In Java lässt sich das hervorragend als record ausdrücken (und ja, das ist der seltene Moment, in dem ein Record nicht nur hilft, Code zu verkürzen, sondern auch das Gehirn zu strukturiertem Denken zwingt).

public record ReviewFinding(
        String area,      // Wo das Problem liegt: z.B. catalog-read / inventory-write
        String symptom,   // Was beobachten wir: "N+1 in der Produktliste"
        String cause,     // Warum passiert das: "Lazy-Navigation + Entity-Loading"
        String evidence,  // Womit belegen wir es: SQL-Trace, Statistiken, konkrete Abfragen
        String decision,  // Was tun wir: Projection / EntityGraph / clear() nach Bulk usw.
        String priority   // Wie dringend: HIGH/MEDIUM/LOW (oder Ihre Skala)
) {}

Beachten Sie: Es gibt kein Feld „wer schuld ist“. Hibernate ist nicht beleidigt, Spring Data auch nicht. Wir dokumentieren das technische Bild. area kann catalog-read oder inventory-write sein; symptom – „N+1 in der Produktliste“; cause – „Entity-Loading für Tabelle + Lazy-Navigation im Mapper“; evidence – „SQL-Trace: 1 + N Abfragen“; decision – „auf Projection umstellen“; priority – „HIGH“.

Schon ein einzelnes Finding hilft dem Team. Und eine Sammlung von Findings verwandelt das Audit in einen gesteuerten Verbesserungsplan. Wichtig ist nur, das letzte Glied nicht zu verlieren: Ein Finding ohne Nachweis ist vorerst eine Hypothese. SQL-Trace, Statistiken und Regressionstests machen daraus eine technische Tatsache.

8. Typische Fehler beim finalen Persistence-Audit

Am letzten Tag ist es leicht, in alte Gewohnheiten zurückzufallen: wieder über Annotationen diskutieren, Code nach Stil beurteilen, Symptome mit universellen Rezepten behandeln. Das ist normal: Das Gehirn liebt einfache Antworten. Aber genau deshalb ist es hilfreich, die typischen Fehler im Voraus zu kennen und sich selbst zu ertappen, bevor Sie EAGER setzen, „damit es kein Lazy gibt“, und dies als Sieg über Hibernate erklären.

Fehler Nr. 1: Das Audit mit Entity-Klassen beginnen, nicht mit dem Anwendungsfall.
Wenn Sie mit dem Durchsehen von Annotationen beginnen, übersehen Sie mit ziemlicher Sicherheit das eigentliche Problem, denn Probleme leben in der Kette „Service → Abfrage → Transaktion → SQL“. Dieselbe Entität kann in einem Szenario normal und in einem anderen schrecklich sein. Beginnen Sie mit einem konkreten Geschäftsablauf und definieren Sie, was für diesen Ablauf als korrektes Ergebnis gilt.

Fehler Nr. 2: Symptom und Ursache verwechseln.
„Wir haben viele Abfragen“ – Symptom. „Wir haben N+1 durch versteckte Navigation im Entity-Graphen in einem Stream/Mapper“ – Ursache. „Wir verwenden Bulk-Update und leeren den Persistence Context nicht“ – Ursache. Wenn Sie sofort „Lösung: JOIN FETCH hinzufügen“ notieren, ohne die Ursache zu dokumentieren, riskieren Sie einen schönen Patch, der die Krankheit nicht heilt oder sie auf Kosten eines anderen Problems heilt.

Fehler Nr. 3: Alle Probleme als gleich wichtig betrachten.
In echten Projekten gibt es immer weniger Zeit für Korrekturen als Probleme. Wenn Sie „Risiken für die Daten“ nicht von „kleinen Unannehmlichkeiten“ trennen, wird das Audit zu einer langen Liste von Anmerkungen, die niemand umsetzt. Gewöhnen Sie sich daran: Correctness und Concurrency auf dem Hot Path haben fast immer Priorität vor lokalen Optimierungen eines seltenen Berichts.

Fehler Nr. 4: Das Audit „nach Gefühl“ ohne Messungen durchführen.
Der Satz „Mir scheint, hier ist N+1“ sollte automatisch den nächsten Schritt auslösen: SQL-Trace/Statistiken aktivieren, eine Baseline erfassen und Fakten prüfen. Das ORM-Verhalten ist beobachtbar. Wenn Sie sich SQL nicht ansehen, diskutieren Sie mit Ihrer eigenen Vorstellungskraft, und die ist in der Regel sehr selbstbewusst und irrt sich sehr oft.

Fehler Nr. 5: Mit einer totalen Neuschreibung beginnen.
Der Wunsch, „alles richtig neu zu schreiben“, ist verständlich, aber gefährlich. Ein gutes Audit führt fast immer zu punktuellen Änderungen: Entity-Loading durch Projection in einer Liste ersetzen, @Version hinzufügen, überflüssiges saveAndFlush() entfernen, einen expliziten Fetch-Plan für die Detailseite erstellen, clear() nach Bulk einfügen. Eine große Neuschreibung ohne Nachweise ist oft einfach eine teure Art, neue Fehler zu bekommen.

1
Aufgabe
Hibernate deep-dive, Level 30, Lektion 0
Gesperrt
Audit-Achsen-Karte für Use-Case
Audit-Achsen-Karte für Use-Case
1
Aufgabe
Hibernate deep-dive, Level 30, Lektion 0
Gesperrt
Wahl der Startachse des Audits
Wahl der Startachse des Audits
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION