Debugging Hibernate

Spurensuche nach einem kuriosen Problem

Ich erfreue mich gerade an einem JPA/Hibernate-Problem, das entweder sehr subtil ist, oder bei dem ich äußerst ungeschickt anstelle :-) Jedenfalls möchte ich die Gelegenheit nutzen, einige Hilfsmittel zu sammeln, mit denen man Hibernate „unter die Haube“ gucken kann.

Hibernate-Logausgaben

Hibernate kann verschiedene Dinge mittels log4j mitprotokollieren. Bei mir als hilfreich haben sich erwiesen:

Protokollieren aller ausgeführter SQL-Statements:

<logger name="org.hibernate.SQL">
    <level value="debug"/>
</logger>

Ausgeben des Transaktions-Status (Beginn und Ende einer Transaktion, sowie Informationen über Stellen, an denen keine Transaktion nötig ist):

<logger name="org.springframework.transaction.interceptor">
    <level value="trace" />
</logger>

Abfrage im Code, ob man sich in einer Transaktion befindet

Ich vermutete mein Problem in diesem Bereich; mittels TransactionAspectSupport.currentTransactionStatus() bekommt man verschiedene Hinweise über den aktuellen Transaktionsstatus, und mit dem hier gezeigten Code kann man sich so etwas wie Assertions bauen, wenn ein Codeabschnitt ohne bestehende Transaktion aufgerufen wird.

Cache-Invalidierung

Obige Vermutungen führten immer noch nicht zum Ziel, nächster Verdacht war irgendein Cache. Hier fand ich Hinweise, wie man den JPA-Cache invalidiert.

Mein Problem?

Ich lade und speichere Daten mittels JPA, anschließend muss ich einige Datenmanipulation per SQL-Befehl ausführen. Dieses Statement greift auf die Daten zu, die ich zuvor per JPA verändert habe - und liest dabei veraltete Daten (vor der Änderung). Ich dachte zunächst, dass das SQL vielleicht außerhalb der Transaktion ausgeführt wird - dem ist nicht so.

Nun habe ich festgestellt, dass das Update auf meinen Daten erst als SQL-Statement im Log erscheint, wenn ich einen weiteren Zugriff (auch nur lesend! Auch per SQL!) mache. Vorher werden meine Änderungen nicht in die Datenbank geschrieben, was das „veraltete-Daten“-Phänomen erklärt. Sehr merkwürdig.

Letztendlich bin ich auf EntityManager.flush() gestoßen. Damit landen ausstehende Operationen in der Datenbank. Wieso ein solcher Flush von einem einfachen SELECT, nicht aber von meinem Update-Statement angestoßen wird (und warum das flush nicht pauschal ausgeführt wird, wenn man native SQL macht), ist mir nicht 100%ig einsichtig…

Zusätzlich stellte sich der Cache-Verdacht auch als korrekt heraus: Nach einem Update via SQL erhielt ich (zumindest innerhalb einer Transaktion) beim Laden eines Objekts veraltete Daten. Zusätzlich war also nach dem SQL-Statement ein EntityManager.refresh(…) notwendig.