1. Beweisgrundlage im Persistence-Review
Wenn Sie zum ersten Mal anfangen, sich mit Hibernate auseinanderzusetzen, scheint die Hauptschwierigkeit darin zu bestehen, sich die Annotationen und die „richtigen Konfigurationen“ zu merken. In der Praxis ist es jedoch etwas anderes, das schwieriger ist: sich mit der Realität zu arrangieren, denn Hibernate tut sehr höflich so, als ob alles „einfach funktioniert“, bis plötzlich in der Produktion 200 Abfragen für einen einzigen Bildschirm auftauchen. Die Beweisgrundlage ist eine Methode, um eine Debatte von „Meinungen“ in „Messungen“ zu überführen, bei der nicht derjenige gewinnt, der lauter spricht, sondern derjenige, der Fakten hat.
Red Flag für sich genommen ist nur ein Verdacht. Damit ein Finding aufhört, ein Satz wie „mir gefällt dieser Code nicht“ zu sein, muss es an Fakten festgemacht werden: an der SQL-Ablaufverfolgung, den Laufzeit-Zählern und einem Test, der das gewünschte Symptom reproduziert.
Im Persistence-Review beantworten wir fast immer eine der drei Fragen: „Welches SQL wurde tatsächlich ausgeführt?“, „Warum wurde es genau so ausgeführt?“ und „Wie können wir garantieren, dass es morgen nicht wieder schlechter wird?“. Wenn Sie sich nicht auf Artefakte stützen, beginnen Sie, nach „Gefühlen“ zu diskutieren, und Gefühle, wie bekannt, lassen sich nicht kompilieren. Und Tests für Gefühle in JUnit wurden bisher noch nicht eingeführt (und Gott sei Dank).
Artefakt der Prüfung: Was zählt als Beweis
Das Wort „Artefakt“ klingt, als ob wir jetzt ein altes Amulett von Hibernate 3.0 ausgraben würden, aber die Bedeutung ist einfacher. Ein Prüfungsartefakt ist ein konkreter Gegenstand, den man einem Review-Finding beifügen kann, damit ein anderer Entwickler die Schlussfolgerung nachvollziehen kann. Ein gutes Artefakt beantwortet die Frage „zeig mir“, nicht „glaub mir“. In unserem Kurs sind diese Artefakte meistens das SQL-Log, numerische Zähler aus Statistics und ein Integrationstest, der das Problem reproduziert.
Es ist sehr wichtig, „Artefakt“ und „Interpretation“ nicht zu verwechseln. Zum Beispiel ist der Satz „hier ist N+1“ eine Interpretation, während „im SQL-Trace 1 Abfrage für die Liste + 50 Abfragen für Kunden nach order.customer_id“ ein Artefakt ist (und es erklärt das Problem fast schon von selbst). Analog ist „überflüssiger Flush“ eine Interpretation, während „im Log ist ein UPDATE vor dem SELECT aufgrund einer überlappenden Abfrage zu sehen“ ein Artefakt ist.
Mini-Zyklus „Baseline → Change → Verify → Lock-in“
Wenn man das Leben auf ein gesundes Minimum reduziert, ist jede Arbeit mit der Persistence-Qualität ein wiederholbarer Zyklus. Zuerst erfassen wir die Baseline (wie es jetzt ist), dann ändern wir die Lösung (Fetch-Plan, Abfrage, Transaktion, Bulk), dann überprüfen wir, ob es besser geworden ist, und schließlich sichern wir dies mit einem Test, damit es nicht zurückfällt. Das ist buchstäblich eine „Reparatur nach Anleitung“, nicht „aufs Fernsehgerät klopfen – es funktioniert“.
Im Folgenden die gleiche Idee in Form eines einfachen Diagramms, damit das Gehirn nicht jedes Mal versucht, das Rad neu zu erfinden:
flowchart TD
A[Baseline: SQL-Ablaufverfolgung + Statistiken] --> B[Change: Code-Änderung]
B --> C[Verify: Erneute Messung]
C --> D[Lock-in: Regressionstest]
D --> A
Auf einem einzigen Hot Path verwandelt sich dieser Zyklus in einen vollwertigen Review-Durchlauf. Wichtig ist für uns zunächst, dass wir lernen, die Baseline stabil zu erfassen und die Änderung zu überprüfen, anstatt blind zu diskutieren.
2. SQL-Trace: ehrlicher Zeuge von Hibernate
Das SQL-Log ist wie eine Überwachungskamera im Geschäft. Man kann diskutieren, „wer den Keks genommen hat“, man kann Theorien aufstellen, man kann sich auf „ich erinnere mich genau“ berufen, aber die Kamera zeigt gelassen: hier die Hand, hier der Keks, hier tut die Person so, als ob sie nur vorbeiging. Hibernate ist in unserem Fall dieser „Passant“, der manchmal so unauffällig einen SELECT macht, dass Sie ihn erst bemerken, wenn die Latenzzeit einbricht. Deshalb ist die SQL-Ablaufverfolgung das erste und stärkste Artefakt.
Im Kurs binden wir bewusst jeden wichtigen Mechanismus von Hibernate an SQL: States, Dirty Checking, Flush, Lazy Loading, Fetching, Bulk Operations, Locking. Und das abschließende Audit sollte ebenfalls in der Sprache von SQL sprechen. Nicht weil „wir Datenbanken lieben“, sondern weil die Datenbank am Ende das ausführt, was Sie ihr gesendet haben, und nicht das, was Sie gemeint haben.
SQL-Trace aktivieren: Profile und Logs
SQL-Trace kann leicht zu einer „Lärmkanone“ werden: einmal eingeschaltet, ähnelt die Konsole der „Matrix“, nur dass statt Code grüne Buchstaben select ... from ... gedruckt werden. Deshalb gilt die Disziplin: Wir aktivieren detailliertes SQL mit einem Profil, nicht für immer. Im Commerce Persistence Lab haben wir dafür das Profil sql-trace, das das Log genau dann detailliert macht, wenn wir das Verhalten diagnostizieren.
Ein minimaler, aber funktionsfähiger Ausschnitt der application-sql-trace.yml sieht etwa so aus (die Schlüssel können je nach Logger-Konfiguration leicht abweichen, aber die Idee ist dieselbe – ein separates Profil und explizite Stufen):
spring:
jpa:
properties:
hibernate:
format_sql: true # SQL formatieren, damit man es wirklich lesen kann
logging:
level:
org.hibernate.SQL: debug # SQL-Strings selbst ausgeben
org.hibernate.orm.jdbc.bind: trace # Bind-Parameter ausgeben (Werte für `?`)
Hier zeigt org.hibernate.SQL die SQL-Strings selbst an, während org.hibernate.orm.jdbc.bind die Bind-Parameter ausgibt (also die Werte, die Sie tatsächlich an ? übergeben haben). Ohne Bind-Parameter wird das SQL-Log oft zu einem Rätsel: „klar, dass er etwas gesucht hat … aber was genau – ein Rätsel des Jahrhunderts“.
Bind-Parameter: Was ohne sie verloren geht
Ein naives SQL-Log ohne Parameter sieht hübscher aus, aber sein Nutzen ist oft geringer. Es zeigt die Form der Abfrage, nicht aber die tatsächliche Selektivität, aufgrund derer sich die Abfrage unterschiedlich verhalten kann. Ein und dasselbe select ... where status = ? kann leicht sein, wenn status=ACTIVE selten vorkommt, und schwer, wenn status=ACTIVE fast alle Zeilen betrifft. Für das Performance-Audit ist das entscheidend.
Ein weiterer Grund, Bind-Parameter zu lieben, ist die Diagnose der Korrektheit. Eine sehr unangenehme Klasse von Fehlern: Sie glauben, Sie filtern „nicht gelöschte“ Produkte, aber in Wirklichkeit vergleichen Sie deleted = null oder haben die Bedingung ganz weggelassen. Wenn man die Parameter sieht, wird das in Sekundenschnelle erkannt, ohne komplizierte philosophische Erörterungen über „mögliche Ursachen“.
SQL-Log lesen: Anzahl, Form, Flush-Spur
SQL-Trace zu lesen bedeutet nicht, „500 Zeilen zu lesen und müde zu werden“. Es ist eine Disziplin: Sie suchen zuerst die Struktur, dann die Details. Normalerweise beginne ich mit drei Fragen: Wie viele Abfragen gibt es im Szenario? Welche Abfragen wiederholen sich in gleicher Form? Und wo tauchen plötzlich UPDATE/INSERT/DELETE auf, wo Sie dachten, „wir lesen doch nur“.
Beispiel eines kleinen Log-Ausschnitts (bedingt, aber sehr ähnlich zu dem, was Sie bereits in den Fetching-Laboren gesehen haben):
-- 1) Bestellung nach ID laden (Root-Entität)
select o.id, o.order_number, o.customer_id
from purchase_order o
where o.id = ?;
-- 2) Dann lazy den Kunden über FK laden (falls der Fetch-Plan ihn nicht vorher „geholt“ hat)
select c.id, c.email
from customer c
where c.id = ?;
Wenn es sich um einen Detail-Use-Case „Bestellung laden und E-Mail des Kunden anzeigen“ handelt, können zwei Abfragen normal sein (abhängig vom Fetch-Plan). Aber wenn Sie ein solches Paar 50 Mal hintereinander in einer einzigen Abfrage einer Bestellliste sehen, ist das fast schon ein Lehrbuchbeispiel für N+1. Das SQL-Trace ist in diesem Sinne ehrlich: Es diskutiert nicht, es zeigt einfach, was Sie tatsächlich getan haben.
Ein weiteres typisches Signal im SQL-Trace ist ein unerwarteter Flush. Stellen Sie sich vor, Sie ändern im Service ein Objekt (Dirty Checking) und führen dann eine Abfrage aus, um „eine Bedingung zu prüfen“. Hibernate kann die Änderungen vor der Ausführung des SELECT in die Datenbank schreiben, um die Korrektheit zu gewährleisten. Im Log sieht das so aus: „Warum habe ich mitten in der Methode ein UPDATE?“. Und genau hier ist das SQL-Trace die einzige Möglichkeit, nicht zu raten.
3. Hibernate Statistics: Laufzeit-Zähler
Statistics ist kein Ersatz für das SQL-Log, sondern ein „Zähler auf dem Armaturenbrett“. Es zeigt nicht die Details jeder Abfrage, liefert dafür aber Zahlen: wie viele Statements vorbereitet wurden, wie viele Entitäten geladen wurden, wie viele Collections initialisiert wurden, wie oft ein Flush stattfand. Das ist praktisch, wenn Sie vergleichen müssen, „es waren 51 Abfragen, jetzt ist es 1“, ohne ein Blatt voller Logs lesen zu müssen. Es gibt jedoch einen Haken: Statistics zählt hervorragend, erklärt aber nicht, daher sollte es zusammen mit dem Use-Case und SQL interpretiert werden.
Wichtig ist nur, diese Zahlen nicht zu überschätzen: prepareStatementCount ist ein guter schneller Zähler für die SQL-Aktivität, aber kein Ersatz für das SQL-Trace selbst. Die Form der Abfrage, die Reihenfolge von SELECT/UPDATE und Flush-Seiteneffekte sehen wir trotzdem im Log.
Im abschließenden Audit ist Statistics besonders nützlich als schneller „Rauchmelder“. Wenn Sie bei einer einzelnen Produktlistenabfrage prepareStatementCount=200 haben, ist das ein Grund, das SQL-Trace zu aktivieren und zu suchen, wo genau Sie einen SQL-Chat veranstaltet haben. Und wenn prepareStatementCount=1 ist, garantiert das nicht, dass die Abfrage gut ist, aber es sagt zumindest, dass Sie wahrscheinlich keinen N+1 gemacht haben.
Statistics aktivieren und im Code verwenden
Damit Statistics überhaupt etwas zählt, muss es aktiviert werden. Üblicherweise geschieht das über die Hibernate-Einstellung hibernate.generate_statistics=true (im Projekt haben wir dafür ein separates Profil stats). Es ist wichtig zu bedenken, dass man Statistiken in der Produktion nicht ständig zählen muss: Es ist ein Diagnose- und Testwerkzeug, kein obligatorischer Bestandteil der Geschäftslogik.
Dann stellt sich die praktische Frage: Wie erhält man im Code das Statistics-Objekt? In einer Spring Boot-Anwendung haben Sie normalerweise eine EntityManagerFactory, die Sie in die Hibernate-SessionFactory „entpacken“ können, und daraus nehmen Sie Statistics. Ein Mini-Ausschnitt, den Sie sowohl in Tests als auch im Lab-Support verwenden werden:
import jakarta.persistence.EntityManagerFactory;
import org.hibernate.SessionFactory;
import org.hibernate.stat.Statistics;
// Native Hibernate API aus dem JPA-Wrapper extrahieren (wir tun dies bewusst für den Deep Dive)
Statistics stats = entityManagerFactory
.unwrap(SessionFactory.class) // EntityManagerFactory in SessionFactory umwandeln
.getStatistics(); // Laufzeit-Zähler holen
Hier ist das Wort unwrap wichtig: Wir geben ehrlich zu, dass unter der JPA-Abstraktion ein bestimmter Provider – Hibernate – steckt, und verwenden seine API bewusst, weil wir einen Deep-Dive-Kurs machen. Das ist kein „Übel“, sondern ein Werkzeug.
Was man in Statistics betrachtet: Wichtige Metriken
Wenn Sie das Interface Statistics öffnen, gibt es viele Methoden, und man kann leicht anfangen, alles Mögliche zu messen, wie ein Anfängersportler, der eine Smartwatch kauft und versucht, die … Mondphase zu analysieren. Für unser abschließendes Audit sind die „bodenständigsten“ Dinge am wichtigsten: wie viele SQL-Operationen, wie viele Entitätsladungen, wie viele Collections initialisiert wurden, wie viele Flush-Zyklen stattfanden. Diese Metriken fügen sich gut in die Audit-Achsen ein: Performance und Korrektheit.
Eine kleine Tabelle (ohne Übertreibung) hilft, den Fokus zu behalten:
| Was wir verstehen wollen | Was in Statistics prüfen | Wie es gewöhnlich interpretiert wird |
|---|---|---|
| „Wie chatty ist das Szenario in Bezug auf SQL?“ | getPrepareStatementCount() | Schneller Proxy für die Anzahl der Prepared Statements; genaue Form und Reihenfolge sehen wir trotzdem im SQL-Trace |
| „Wie viele Entitäten wurden tatsächlich geladen?“ | getEntityLoadCount() | Verständnis der Materialisierungsbreite (und potenzielles Overfetching) |
| „Wie viele Collections wurden initialisiert?“ | getCollectionFetchCount() | Oft ein Signal für verdeckte Lazy-Navigation über To-Many |
| „Wie oft trat ein Flush auf?“ | getFlushCount() | Hilft, unerwartete Flush-Trigger zu erkennen |
Ja, das ist eine Vereinfachung, aber wir brauchen ein funktionierendes Werkzeug, kein Metrik-Museum. Im finalen Review sollten Sie schnell Fragen beantworten, nicht eine Dissertation darüber schreiben, warum prepareStatementCount um 2 gestiegen ist.
Mini-Helfer fürs Lab-Support: Statistik-Snapshot vorher/nachher
Ein sehr praktischer Trick für Labore und Tests ist es, einen „Snapshot“ der Statistik zu Beginn und am Ende eines Szenarios zu machen. Dann diskutieren Sie nicht über „viel/wenig“, sondern sagen: „Es waren 0, jetzt sind es 51“. In Java lässt sich das bequem mit einem kleinen Record umsetzen (und ja, Record passt hier perfekt: ein einfaches DTO ohne zusätzliche Logik).
import org.hibernate.stat.Statistics;
// Mini-DTO: Speichern, was wir vorher/nachher vergleichen wollen
public record StatsSnapshot(long statements, long entityLoads) {
// Snapshot aus dem aktuellen Zustand der Statistics erstellen
public static StatsSnapshot of(Statistics stats) {
return new StatsSnapshot(
stats.getPrepareStatementCount(), // wie viele Prepared Statements Hibernate vorbereitet hat
stats.getEntityLoadCount() // wie viele Entitäten tatsächlich geladen wurden
);
}
}
Dann können Sie im Test oder Lab-Code Folgendes tun: stats.clear(), den Use-Case ausführen, StatsSnapshot.of(stats) holen und vergleichen. Das ersetzt kein SQL, funktioniert aber hervorragend als „digitaler Scheck“ für ein Review-Finding.
4. ORM-Regressionstests: Verhalten absichern
Tests in der Persistence-Schicht drehen sich nicht so sehr darum, „ob die Daten korrekt gespeichert wurden“ (das ist auch wichtig), sondern vielmehr darum, „ob sich das System korrekt verhält“. ORM-Fehler sind tückisch, weil der Code „logisch korrekt“ bleibt, aber teuer oder unvorhersehbar wird. Daher sind Regressionstests für ORM eine Versicherung gegen das Wiederauftreten alter Probleme nach einem „harmlosen“ Refactoring, einem neuen Mapper oder einer Änderung des Fetch-Plans.
Und hier gibt es eine wichtige psychologische Sache: Ein guter ORM-Test ist einer, bei dem Sie das Symptom überprüfen, das tatsächlich in der Produktion auftritt. Zum Beispiel: N+1, ein veralteter Persistence Context nach einem Bulk, ein überflüssiger Flush, ein Optimistic Conflict. Zu prüfen, „was Hibernate für SQL macht“, ist sinnlos. Zu prüfen, „dass wir keine 51 Prepared Statements für eine Liste gemacht haben“, ist sehr sinnvoll.
Integrationskontext statt Unit-Tests
Ein Unit-Test ist ein Test der Logik im Vakuum. ORM lebt jedoch nicht im Vakuum, sondern in einer Kombination: Transaktion, Persistence Context, JDBC, Datenbank, Isolation, Ausführungspläne. Daher gleicht ein Unit-Test für ein Repository mit Mocks in der Regel einem Test eines Flugzeugs im Zimmer: Sie können überprüfen, ob der Knopf „Fahrwerk“ die Methode lowerLandingGear() aufruft, aber Sie werden nicht überprüfen, ob das Flugzeug tatsächlich landet.
Das bedeutet nicht, dass Unit-Tests schlecht sind. Es bedeutet, dass Sie für das ORM-Verhalten einen Integrationskontext benötigen: zumindest @DataJpaTest mit einer echten Datenbank (in unserem Kurs ein PostgreSQL-orientierter Ansatz). Und in diesem Kontext können Sie SQL, Statistics messen und Fehler finden, die in Mocks einfach nicht existieren.
Testvorlage für N+1: Messen, nicht glauben
Ein Test auf N+1 sieht meistens so aus: Wir starten einen bestimmten Lese-Use-Case, setzen die Statistik zurück, führen die Abfrage aus und behaupten, dass der Zähler der Prepared Statements der Erwartung entspricht. Das ist keine perfekte Metrik, aber als Regression-Versicherung funktioniert es hervorragend: Wenn morgen jemand den EntityGraph entfernt oder die Abfrage auf findAll() umstellt, wird der Test anfangen zu schreien.
Mini-Ausschnitt aus einem Test (innerhalb von @DataJpaTest oder @SpringBootTest – je nachdem, welchen Teil Sie prüfen):
import org.hibernate.SessionFactory;
import org.hibernate.stat.Statistics;
// Given: Statistics aus Hibernate holen (im Test ist das legal und nützlich)
Statistics stats = entityManager.getEntityManagerFactory()
.unwrap(SessionFactory.class)
.getStatistics();
stats.clear(); // Given: Zähler zurücksetzen, um nur dieses Szenario zu messen
// When: Detail-Use-Case ausführen
orderRepository.findDetailedById(orderId).orElseThrow();
// Then: Regression anhand der Anzahl der Prepared Statements in diesem Szenario feststellen
assertThat(stats.getPrepareStatementCount()).isEqualTo(1);
Beachten Sie das „Ritual“ stats.clear(). Wenn man es vergisst, misst man nicht das Szenario, sondern die gesamte Historie des Testprozesses. Der Test wird dadurch instabil und verhält sich wie eine Wahrsagerin: „heute 3, morgen 7“.
Und noch ein Punkt: Dieser Test sollte eine bestimmte Methode prüfen, die für den Detail-Use-Case vorgesehen ist (z. B. findDetailedById). Wenn Sie den „normalen“ findById prüfen, kann die Erwartung von „1 Abfrage“ falsch sein – Sie haben einfach einen anderen Vertrag.
Bulk-Test: Veralteter Zustand und clear()
Bulk-Update/-Delete ist eine separate „Schule des Schmerzes“, weil Bulk die Daten in der Datenbank ändert, aber nicht die bereits geladenen Managed-Objekte im Persistence Context synchronisiert. Daher fixiert ein korrekter Regressionstest hier normalerweise nicht nur die Bulk-Operation, sondern auch das Protokoll danach: clear() oder erneutes Lesen.
Mini-Idee eines solchen Tests am Beispiel von Product:
import jakarta.persistence.EntityManager;
// Given: Managed-Entität in den Persistence Context laden
Product p = em.find(Product.class, productId);
// When: Bulk-Update durchführen (umgeht den Persistence Context und trifft direkt die DB)
productRepository.markDeleted(productId);
// Then: Mit der DB synchronisieren, sonst lesen wir weiterhin das veraltete Objekt
em.clear(); // Veraltete Objekte aus dem Persistence Context entfernen
Product reloaded = em.find(Product.class, productId); // Aktuellen Zustand aus der DB lesen
assertThat(reloaded.isDeleted()).isTrue();
Dieser Ausschnitt ist gut, weil er sehr „menschlich“ ist: Sie zeigen buchstäblich, dass Sie nach einem Bulk dem Objekt p nicht vertrauen dürfen, und Sie machen den korrekten Synchronisierungsschritt. Ein solcher Test schützt Sie in Zukunft vor einem „Optimierer“, der clear() entfernen möchte, „weil es scheinbar nicht nötig ist“.
Lesbarkeit des Tests: Given/When/Then in 10 Zeilen
Das Problem bei ORM-Tests ist nicht nur, sie zu schreiben, sondern auch, dass Sie selbst nach einem Monat noch verstehen, was Sie gemessen haben. Daher ist es auch bei kurzen Tests hilfreich, sie als „Given/When/Then“ zu strukturieren, zumindest mit Kommentaren. Das ist keine Religion, sondern ein Mittel zum Schutz vor einem „Test-Rätsel“.
Beispiel einer Mikrostruktur direkt im Code:
stats.clear(); // Given: saubere Baseline, vorherige SQL/Flush interessieren uns nicht
orderQuery.findOrderPage(); // When: Use-Case ausführen (das messen wir)
long sql = stats.getPrepareStatementCount(); // Then: messbare Tatsache zur SQL-Aktivität erhalten, kein „Gefühl“
assertThat(sql).isLessThanOrEqualTo(2); // Then: Vertrag festlegen (und Regression erkennen)
Ja, das sieht fast lächerlich einfach aus. Aber genau solche einfachen Kommentare machen Persistence-Tests lehrreich und wartbar, anstatt „magische Zahlen ohne Kontext“ zu sein.
Verknüpfung von SQL + Statistics + Tests als eine Sprache
Eine starke technische Position im Persistence-Review sieht so aus: „Ich sehe ein Problem, ich kann es im SQL zeigen, ich kann es mit einer Zahl bestätigen, und ich kann die Korrektur mit einem Test absichern.“ Das sind drei Ebenen ein und derselben Wahrheit. SQL zeigt die Details, Statistics liefert schnelle numerische Signale, der Regressionstest gibt die Garantie, dass Sie die Lektion nicht in einer Woche vergessen.
Damit dieser Ansatz zu einem „Muskelgedächtnis“ wird, ist es hilfreich, eine einfache Matrix griffbereit zu haben. Nicht als Liste von 50 Punkten, sondern als „wenn jemand nach X fragt – beweise Y so“.
| Review-Frage | Womit belegen wir | Minimale Prüfung |
|---|---|---|
| „Hier wirklich N+1?“ | SQL-Trace + Statistics | Im Log sind wiederholte select ... where id=? zu sehen, und prepareStatementCount zeigt einen deutlichen Anstieg der SQL-Aktivität |
| „Warum erscheint ein UPDATE vor Methodenende?“ | SQL-Trace | Im Log UPDATE vor SELECT, im Code überlappende Abfrage oder Flush-Trigger |
| „Ist die Bulk-Operation in dieser Transaktion sicher?“ | Test + clear()-Protokoll | Regressionstest zeigt veralteten Zustand ohne clear() und Korrektheit mit clear() |
| „Bläht dieser Fetch-Plan das Ergebnis auf?“ | SQL-Trace | Eine Abfrage, aber riesiger Join und Duplikate der Root-Entität (erkennbar an der SQL-Form/DISTINCT) |
| „Haben wir die optimistische Sperre verloren?“ | Concurrency-Test + SQL | Test reproduziert Konflikt, SQL update ... where version=? bestätigt Versionierung |
Der Sinn der Tabelle ist, dass sie Sie dazu zwingt, nicht zu denken „welches Werkzeug mag ich“, sondern „welche Behauptung stelle ich auf und womit belege ich sie“. Das ist evidenzbasiertes Review.
Wenn Hot Path, Red Flags und Evidenz nebeneinander liegen, hört das Review auf, eine Theorie zu sein, und fügt sich in einem Durchgang zusammen: Service → Abfrage → SQL → Finding → Priorität.
5. Typische Fehler beim Sammeln von Beweisen
In der Beweisgrundlage gibt es ein paar Fallstricke, die besonders Anfänger lieben. Sie klingen „in Worten“ logisch, führen aber dazu, dass das Audit entweder zu Lärm oder zu einem Ritual wird. Lassen Sie uns sie so besprechen, dass Sie sie am Geruch erkennen, wie ein Programmierer ein Speicherleck erkennt: „es funktioniert irgendwie … aber der Lüfter schreit wie ein Düsentriebwerk“.
Fehler Nr. 1: SQL-Trace für immer einschalten und aufhören, es zu lesen.
Wenn das SQL-Log immer eingeschaltet ist, hören Sie sehr schnell auf, es wahrzunehmen. Das ist wie das Leben neben einer Eisenbahnlinie: Die ersten beiden Tage wachen Sie bei jedem Zug auf, am dritten Tag gibt das Gehirn auf. Am Ende haben Sie „Beobachtbarkeit eingeschaltet“, aber sie funktioniert nicht. Besser ist es, das detaillierte SQL in einem separaten Profil zu behalten und es bewusst für ein Szenario einzuschalten.
Fehler Nr. 2: Statistics ohne Szenario-Isolation messen.
Eine sehr häufige Situation: Sie haben prepareStatementCount angesehen, die Zahl 37 gesehen und den Schluss gezogen „schrecklich, N+1“. Und dann stellt sich heraus, dass diese 37 von drei verschiedenen Tests stammen, weil die Statistik nicht zurückgesetzt wurde. stats.clear() vor dem Szenario ist keine „überflüssige Zeile“, sondern die Grenze des Experiments. Ohne sie werden Zahlen zum Ratespiel.
Fehler Nr. 3: Anzunehmen, dass „1 SQL“ automatisch „alles ist gut“ bedeutet.
Eine einzelne Abfrage kann wunderbar sein, aber auch ein Monster mit 12 Joins, doppelten Zeilen und enormer Ergebnisspaltenbreite. Statistics sagt dann „1“, aber die Performance ist trotzdem schlecht. Daher ist „1 SQL“ nur die Antwort auf die Frage „Gibt es N+1?“, kein Qualitätsmerkmal für die gesamte Abfrage. Für die Qualität ist immer noch ein Blick auf die SQL-Form und das Verständnis des Use-Cases nötig.
Fehler Nr. 4: ORM-Tests als „Hibernate-Prüfung“ schreiben, nicht als Prüfung unseres Vertrags.
Ein Test „Hibernate macht einen Flush beim Commit“ ist sinnlos: Er ist entweder immer grün oder bricht bei einem Versions-Upgrade. Ein Test „unsere Methode findDetailedById sollte nicht mehr als 1 Prepared Statement machen“ ist nützlich, weil es Ihr Vertrag ist. ORM-Regressionstests sollten genau Ihre Erwartungen an die Persistence-Schicht schützen, nicht die universellen Regeln von ORM.
Fehler Nr. 5: Artefakte zur Religion machen: „SQL ist heilig, Tests werden alles lösen“.
SQL-Trace und Tests sind kein Ziel, sondern Werkzeuge. Wenn Sie anfangen, jede Kleinigkeit mit drei verschiedenen Methoden zu „beweisen“, verschwenden Sie mehr Zeit mit Messen als mit Korrigieren. Halten Sie die Balance: zuerst das Signal (Statistics), dann die Erklärung (SQL), dann die Absicherung (Test) – genau dort, wo das Risiko einer Regression und ein echter Wert liegen.
GO TO FULL VERSION