Sonntag, Dezember 30, 2007

 

Hibernate, Spring, Transaktionen auf Service-Ebene

Anwendungen, die Hibernate zur Persistierung benutzen, treffen gelegentlich auf das Problem einer org.hibernate.LazyInitializationException. Diese Exception tritt auf, wenn eine Instanz aus der Datenbank geholt wird, die Referenzen auf andere persistente Instanzen besitzt und diese Referenzen mit lazy="true" im hibernate-mapping gekennzeichnet ist. Dieses lazy="true" bedeutet, dass die abhängigen Instanzen bei Bedarf - und damit beim ersten Zugriff darauf - nachgeladen werden. Hibernate benutzt dazu Proxies, die beim ersten Zugriff auf die Referenz diese aus der Datenbank nachladen und diese gegen sich selber austauschen. Die LazyInitializationException tritt nun auf wenn man eine Instanz mit uninitialisierten (lazy) Referenzen besitzt und versucht auf diese Referenzen zuzugreifen und bedauerlicherweise vorher die Session geschlossen hat. Dann weiss Hibernate nicht wie es die Referenzen holen soll und dann knallts.

Hibernate Sessions. Wenn man direkt mit Hibernate rummacht, dann muss man sich mehr oder weniger selbst darum kümmern wann die Session geöffnet und geschlossen wird. Dazu gibt es so einige Ansätze wie man das schlauerweise macht, z.B. für Webanwendungen den OpenSessionInViewFilter. Diese Ansätze führen dazu, dass immer eine geöffnete Session vorhanden ist. Das hilft allerdings auch nicht, wenn ein persistentes Objekt mit lazy Referenzen in die HttpSession gekippt wird und in einem späteren Request/Response-Zyklus versucht wird die lazy Referenzen aufzulösen. Hier hätte das persistente Objekt mit merge() oder update() erstmal wieder an die aktuelle Session gebunden werden müssen.

Transaktionen. Eine gute Anwendung sollte Transaktionen benutzen und zwar Transaktionen auf Serviceebene. Viele Beispiele zur Verwendung von Spring und Hibernate zeigen wie Transaktionen auf Dao-Ebene funktionieren. Aber ist das sinnvoll ? Ich denke nein. Exceptions auf Dao-Ebene haben entweder technischen Hintergrund, wie verlorene Datenbankverbindungen, oder Verletzung von Constraints wie NOT NULL oder referenzieller Integrität. Außerdem sind die Aktionen in Daos doch eher simpel INSERT, UPDATE, DELETE, ein paar mehr oder weniger kompexe SELECTs. Alles nicht wirklich spannend für Transaktionen. Betrachtet man hingegen den Service-Layer, der per Definition die Geschäftlogik beinhaltet, dann findet man hier oft Methoden, die sich aus verschiedenen Daos bedienen und ihre Ergebnisse über ein oder mehrere Daos in die Datenbank fliessen lassen. Die Operationen sind deutlich komplexer - eben Businesslogik. Hier kann schon viel eher mal was schief gehen und zwar mehr Ausnahmen im Sinne der Businesslogik - deshalb sollte zu jedem Service auch eine passende Exception existieren und bei Bedarf geworfen werden. Ausnahmen, die die bisherigen Änderungen an der Datenbank wieder zurücknehmen sollen und dann an den Aufrufer weitergegeben werden. Hier sind die Transaktionen wirklich sinnvoll. Praktischerweise funktioniert das automatisch, wenn die Service-Exception von RuntimeException erbt. Ein hübscher Vorteil von Transaktionen auf Service-Ebene ist, dass hier die Hibernate-Session die Gleiche ist damit das verwenden von lazy Referenzen im Service-Layer kein Problem darstellt, weil alle "lazy"-Referenzen transparent aufgelöst werden.

Entkopplung auf Service-Ebene. Die "Kunden" eines Service-Layers sollen die Rückgabewerte ohne Einschränkungen verwenden können. Da die Session aber mit Ende der Transaktion ebenfalls geschlossen wird, ist die Verwendung von "lazy"-Referenzen jenseits des Service-Layers nicht mehr möglich. Das bedeutet, dass man aus Sicht des Service Layers entweder sehr genau wissen muss, welche der Lazy-Referenzen vom Kunden verwendet werden um diese gezielt zu initialisieren oder eben alle potentiell nutzbaren Referenzen zu initalisieren. Für die selektive Initialisierung kann man Frameworks benutzen, z.B. ServiceDataObject Framework. Das hat allerdings den Nachteil, das der Kunde sich Gedanken über die Verwendungstiefe machen muss. Gelegentlich ist der Kunde aber auch nur ein weiterer Layer, der dann diese Gedanken auch seinem Kunden zumuten muss, usw. Die andere Alternative ist die voll Initialisiering der Rückgabewerte. Je nachdem wie exzessiv das Hibernate-Mapping betrieben wurde, kann es passieren, dass man nach einem einzigen Aufruf die ganze Datenbank als Objektbaum im Hauptspeicher hat. Dennoch ist es aus Sicht des Kunden optimal, weil er innerhalb der Objektstruktur problemlos navigieren kann.

Meine Best Practices und Regeln. Ich verwende in meinen Projekten Transaktion auf Service-Ebene und gebe aus dieser nur voll initialisierte Objektbäume zurück. Ich verwende für die Transaktionen und die Initialisierung von Rückgabewerten Spring-AOP. Diese Vorgehensweise funktioniert allerdings nur, wenn für die Architektur des Service-Layers und für das Hibernate-Mapping einige Regeln beachtet werden.
  1. Fortpflanzung Begrenzen. Es muss immer beachtet werden, das ein Objekt, dass über den Service-Layer bezogen wird alle referenzierten Objekt einschließt. Das bedeutet, dass verschachtelte und inverse 1:n Beziehungen keine gute Idee sind, weil die Initialisierungsroutine alles Initialisiert was erreichbar ist - und das ist in diesem Fall alles. Man muss also beim Mapping Schranken vorsehen die die Fortpflanzung in zu große Objektstrukturen verhindern.
  2. Fetter Service-Layer. Der Service-Layer an sich wird tendentiell mehr Methoden enthalten, weil nur eine persistente Objektinstanz zurückgegeben werden sollte, wenn diese mit ihren Abhängigkeiten auch wirklich benötigt wird. Benötigt man zum Beispiel eine Liste mit Namen von Produkten, dann ist der Rückgabewert ein String[] und nicht eine List. Innerhalb der Service-Methode kann über die List iteriert werden um das String-Array aufzubauen, aber innerhalb des Service-Layers können die Abhängigkeiten von der Product-Instanzen "lazy" bleiben. Der Service-Layer wird durch diese Art der Verwendung etwas aufgebläht, weil es für jeden kram eine extra Methode gibt, aber das ist ein kleiner Preis für eine problemlose Verwendung.

Labels: , , ,


This page is powered by Blogger. Isn't yours?