Abrufen von "org.hibernate.LazyInitializationException" -Ausnahmen nach dem Abrufen von Elementen aus meinem ehcache der zweiten Ebene

8

Ich verwende Hibernate 5.1.0.Final mit ehcache und Spring 3.2.11.RELEASE. Ich habe die folgende @Cacheable Annotation in einem meiner DAO s eingerichtet:

%Vor%

Das zurückgegebene Element hat eine Reihe von Assoziationen, von denen einige faul sind. Zum Beispiel verweist es (irgendwann) auf das Feld:

%Vor%

Ich stelle fest, dass ich innerhalb einer meiner Methoden, die ich als @Transactional ankreuze, beim Abrufen der obigen Methode aus dem Cache der zweiten Ebene die folgende Ausnahme erhalte, wenn ich versuche, das Feld categories zu durchlaufen:

%Vor%

Die Stapelverfolgung ist:

%Vor%

Ich verstehe, dass die Hibernate-Sitzung geschlossen ist - mir ist es egal, warum das passiert. Es ist auch KEINE Option, die obige Assoziation eifrig zu machen (statt faul). Wie kann ich dieses Problem lösen?

Bearbeiten: Hier ist wie meine ehccahe.xml konfiguriert ist ...

%Vor%

und hier ist, wie ich es in meinen Spring-Kontext einfüge ...

%Vor%     
Dave 14.03.2016, 20:35
quelle

3 Antworten

5

Sehen Sie sich eine ähnliche Frage an. Grundsätzlich ist Ihr Cache kein Second-Level-Cache von Hibernate. Sie greifen auf eine nicht initialisierte faule Assoziation auf einer Instanz einer gelöschten Entität zu, sodass erwartet wird, dass ein LazyInitializationException ausgelöst wird.

Sie können versuchen, mit hibernate.enable_lazy_load_no_trans herumzuspielen, aber die empfohlene Vorgehensweise besteht darin, Hibernate Second-Level-Cache , sodass:

  • Im Cache gespeicherte Entitäten werden automatisch den nachfolgenden Sitzungen zugeordnet, in die sie geladen werden.
  • Zwischengespeicherte Daten werden im Cache automatisch aktualisiert / ungültig gemacht, wenn sie geändert werden.
  • Änderungen an den zwischengespeicherten Instanzen werden unter Berücksichtigung der Transaktionssemantik synchronisiert. Änderungen sind für andere Sitzungen / Transaktionen mit der gewünschten Ebene der Cache / db Konsistenzgarantien sichtbar.
  • Zwischengespeicherte Instanzen werden automatisch aus dem Cache abgerufen, wenn sie zu den anderen Entitäten navigiert werden, mit denen sie assoziiert sind.

BEARBEITEN

Wenn Sie den Spring-Cache dennoch für diesen Zweck verwenden möchten oder Ihre Anforderungen dergestalt sind, dass dies eine angemessene Lösung ist, beachten Sie, dass Hibernate-verwaltete Entitäten nicht threadsicher sind. Daher müssen Sie distanziert speichern und zurückgeben Entitäten zum / vom benutzerdefinierten Cache. Vor der Trennung müssen Sie außerdem alle verzögerten Zuordnungen initialisieren, auf die Sie beim Trennen der Entität zugreifen möchten.

Um dies zu erreichen, könnten Sie:

  1. Trennen Sie die verwaltete Entität explizit von EntityManager.detach Sie müssten die Operation auch von den zugehörigen Entitäten trennen oder kaskadieren und sicherstellen, dass die Verweise auf die gelöschten Entitäten von anderen verwalteten Entitäten entsprechend behandelt werden.
  2. Oder Sie können dies in einer separaten Transaktion ausführen, um sicherzustellen, dass alles losgelöst ist und Sie im aktuellen Persistenzkontext nicht auf losgelöste Entitäten von den verwalteten Entitäten verweisen:

    %Vor%

    Da der Spring-Transaktionsproxy (Interceptor) möglicherweise vor dem Cache-Proxy ausgeführt wird (beide haben denselben Standardwert order : Transaktion ; cache ), dann würden Sie immer eine verschachtelte Transaktion starten, sei es, um die Entität wirklich zu holen, oder um einfach zurückzukehren die zwischengespeicherte Instanz.

    Auch wenn wir zu dem Schluss kommen, dass die Leistung beim Starten nicht benötigter verschachtelter Transaktionen gering ist, besteht das Problem hier darin, dass Sie ein kleines Zeitfenster zurücklassen, wenn eine verwaltete Instanz im Cache vorhanden ist.

    Um dies zu vermeiden, könnten Sie die Standardwerte für die Reihenfolge ändern:

    %Vor%

    , so dass der Cache-Interzeptor immer vor der Transaktion platziert wird.

    Um Konfigurationsänderungen zu vermeiden, können Sie den Aufruf einfach von der Methode @Cacheable an die Methode @Transactional(propagation = Propagation.REQUIRES_NEW) einer anderen Bean delegieren.

Dragan Bozanovic 18.03.2016, 16:01
quelle
9

Was Sie in Ihren Code-Snippets implementiert haben, ist ein benutzerdefinierter -Cache, der auf dem Spring-Cache basiert. Bei Ihrer Implementierung müssen Sie auf Cache-Räumungen achten und sicherstellen, dass die Objektgraphen zu dem Zeitpunkt, zu dem sie zwischengespeichert werden, ordnungsgemäß geladen werden. Sobald sie zwischengespeichert sind und die ursprüngliche Ruhezustand-Sitzung geschlossen wurde, wird sie geschlossen losgelöst werden, können Sie nicht mehr durch nicht abgerufene faule Assoziationen navigieren. Außerdem würde Ihre benutzerdefinierte Cache-Lösung in ihrem aktuellen Zustand Entitätsdiagramme zwischenspeichern, was wahrscheinlich nicht Ihren Vorstellungen entspricht, da sich jeder Teil des Diagramms zu einem bestimmten Zeitpunkt ändern kann und Ihre Cache-Lösung dies erfordern würde achten Sie auf Änderungen in allen Teilen dieses Diagramms, um Räumungen ordnungsgemäß zu behandeln.

Die Konfiguration, die Sie in Ihrer Frage gepostet haben, ist kein Second-Level-Cache von Hibernate .

Das Verwalten eines Caches ist ein komplexes Unterfangen und ich empfehle es nicht, es selbst zu tun, es sei denn, Sie sind absolut sicher, was Sie tun (aber dann werden Sie diese Frage nicht auf Stackoverflow stellen) .

Lassen Sie mich erklären, was passiert, wenn Sie LazyInitializationException erhalten: Sie haben eine Ihrer dao-Methoden mit @org.springframework.cache.annotation.Cacheable markiert. Was in diesem Fall passiert, ist folgendes:

  1. Spring fügt Ihrer verwalteten Bean einen Interceptor hinzu. Der Interceptor wird den Dao-Methodenaufruf abfangen, einen Cacheschlüssel basierend auf der Interceptor-Methode und den tatsächlichen Methodenargumenten erstellen (dies kann angepasst werden) und den Cache nachschauen, um festzustellen, ob im Cache für diesen Schlüssel ein Eintrag vorhanden ist. Falls es einen Eintrag gibt, wird dieser Eintrag zurückgegeben, ohne Ihre Methode tatsächlich aufzurufen. Falls für diesen Schlüssel kein Cache-Eintrag vorhanden ist, ruft er Ihre Methode auf, serialisiert den Rückgabewert und speichert ihn im Cache.
  2. Für den Fall, dass für den Schlüssel kein Cache-Eintrag vorhanden war, wird Ihre Methode aufgerufen. Ihre Methode verwendet einen Spring-Singleton-Proxy für die Thread-Grenze EntityManager , die früher zugewiesen wurde, als Spring den ersten Methodenaufruf @Transactional ermittelte. In Ihrem Fall war dies die getContent(...) Methode einer anderen Spring Service Bean. Ihre Methode lädt also eine Entität mit EntityManager.find() . Dadurch erhalten Sie ein teilweise geladenes Entitätsdiagramm mit nicht initialisierten Proxys und Sammlungen für andere verknüpfte Entitäten, die noch nicht vom Persistenzkontext geladen wurden.
  3. Ihre Methode kehrt mit dem teilweise geladenen Entitätsdiagramm zurück, und spring serialisiert es sofort für Sie und speichert es im Cache. Beachten Sie, dass die Serialisierung eines teilweise geladenen Entitätsdiagramms zu einem teilweise geladenen Entitätsdiagramm deserialisiert wird.
  4. Beim zweiten Aufruf der mit @Cacheable markierten Dao-Methode mit denselben Argumenten wird Spring feststellen, dass tatsächlich ein Eintrag im Cache vorhanden ist, der diesem Schlüssel entspricht, und den Eintrag laden und deserialisieren. Ihre dao-Methode wird nicht aufgerufen, da sie den zwischengespeicherten Eintrag verwendet. Jetzt tritt das Problem auf: Ihr deserialisiertes, im Cache gespeichertes Entitätsdiagramm wurde nur teilweise geladen, wenn Sie im Cache gespeichert wurden. Sobald Sie einen nicht initialisierten Teil des Diagramms berühren, erhalten Sie LazyInitializationException . Eine deserialisierte Entity wird immer losgelöst sein. Selbst wenn das ursprüngliche EntityManager noch offen wäre (was nicht der Fall ist), erhalten Sie immer noch dieselbe Ausnahme.

Jetzt ist die Frage: Was können Sie tun, um LazyInitializationException zu vermeiden? Nun, meine Empfehlung ist, dass Sie vergessen, einen benutzerdefinierten Cache zu implementieren und nur Hibernate zu konfigurieren, um das Caching für Sie durchzuführen. Ich werde später darüber reden. Wenn Sie bei dem benutzerdefinierten Cache bleiben möchten, den Sie implementieren wollten, müssen Sie Folgendes tun:

Gehen Sie durch Ihre gesamte Codebasis und finden Sie alle Aufrufe Ihrer @Cacheable dao-Methode. Befolgen Sie alle möglichen Codepfade, in denen das geladene Entitätsdiagramm herumgereicht wird, und markieren Sie alle Teile des Entitätsdiagramms, die vom Client-Code berührt werden. Gehe jetzt zurück zu deiner @Cacheable -Methode und modifiziere sie so, dass sie alle Teile des Entity-Graphen lädt und initialisiert, die jemals berührt werden könnten. Weil, sobald Sie es zurückgeben und es serialisiert und später deserialisiert wird, wird es immer in einem getrennten Zustand sein, also stellen Sie sicher, dass alle möglichen Graphpfade korrekt geladen sind. Du solltest schon fühlen, wie unpraktisch das enden wird. Wenn dich das noch nicht davon überzeugt hat, dieser Richtung zu folgen, hier ist ein weiteres Argument.

Da Sie einen potenziell großen Teil der Datenbank laden, haben Sie einen Snapshot dieses Teils der Datenbank zu dem Zeitpunkt, an dem er tatsächlich geladen und zwischengespeichert wurde. Wenn Sie nun eine zwischengespeicherte Version dieses großen Teils der Datenbank verwenden, besteht das Risiko, dass Sie eine veraltete Version dieser Daten verwenden. Um dies zu verhindern, müssen Sie nach Änderungen in der aktuellen Version des großen Teils der gerade zwischengespeicherten Datenbank Ausschau halten und das gesamte Entitätsdiagramm aus dem Cache entfernen.Sie müssen also ziemlich genau berücksichtigen, welche Entitäten Teile Ihres Entity-Graphen sind, und einige Event-Listener einrichten, wenn diese Entitäten geändert werden und den gesamten Graphen entfernen. Keines dieser Probleme ist mit dem Second-Level-Cache von Hibernate vorhanden.

Nun zurück zu meiner Empfehlung: Richten Sie den Second-Level-Cache für den Ruhezustand ein

Der Hibernate-Cache der zweiten Ebene wird von Hibernate verwaltet und Sie erhalten die Räumungsverwaltung automatisch aus dem Ruhezustand. Wenn Sie den Second-Level-Cache von Hibernate aktiviert haben, speichert Hibernate die Daten, die für die Rekonstruierung Ihrer Entitäten benötigt werden. Wenn er eine Entität aus der Datenbank lädt, findet er einen gültigen Cache-Eintrag Für Ihre Entität wird das Überspringen der Datenbank übersprungen und die Entität aus ihrem Cache rekonstruiert. (Markieren Sie den Unterschied zum Zwischenspeichern eines Entitätsdiagramms mit seinen möglicherweise nicht abgerufenen Zuordnungen und nicht initialisierten Proxys in Ihrer benutzerdefinierten Cache-Lösung) . Es wird auch veraltete Cache-Einträge ersetzen, wenn Sie eine Entität aktualisieren. Es macht alle möglichen Dinge im Zusammenhang mit der Verwaltung des Cache, so dass Sie sich keine Sorgen machen müssen.

So können Sie den Second-Level-Cache von Hibernate aktivieren: Führen Sie zusätzlich zu Ihrer Konfiguration Folgendes aus:

  1. Zusätzlich zu den Hibernate-Eigenschaften, die Sie bereits für die Verwaltung auf der zweiten Ebene haben, nämlich

    %Vor%

    füge den folgenden Eintrag hinzu:

    %Vor%

    alternativ können Sie eine shared-cache-mode -Konfigurationsoption zu Ihrer persistence.xml hinzufügen (da Sie sie nicht gepostet haben, nehme ich an, dass Sie sie nicht verwenden, daher die vorherige Alternative; das Folgende man ist aber bevorzugt):

    %Vor%
  2. Fügen Sie javax.persistence.@Cacheable Annotation zu Ihren @Entity -Klassen hinzu, die cachefähig sein sollen.
  3. Wenn Sie Caching für sammlungsbewertete Zuordnungen hinzufügen möchten, die Hibernate standardmäßig nicht zwischenspeichert, können Sie eine @org.hibernate.annotations.Cache Annotation (mit einer geeigneten Cache-Concurrency-Strategie) für jede solche Sammlung hinzufügen:

    %Vor%

Siehe Verbesserung der Leistung / Zweite Ebene Cache in der Hibernate-Referenzdokumentation für weitere Details.

Dies ist ein netter informativer Artikel über das Thema: Fallstricke der Secondary-Level- / Query-Caches des Hibernate

Ich habe ein kleines Projekt zusammengestellt, das auf Ihren veröffentlichten Code-Snippets basiert, die Sie überprüfen können, um Hibernate zu sehen. Level-Cache in Aktion.

    
Nándor Előd Fekete 23.03.2016 01:04
quelle
3

Das Problem besteht darin, dass Sie Verweise auf Objekte speichern, die träge geladen sind. Cache das Objekt, sobald es alle geladen ist oder den Cache überhaupt nicht verwenden.

So können Sie die Kategorien manuell laden, bevor Sie sie zwischenspeichern:

%Vor%

Eine bessere Caching-Strategie wäre auch, den Cache auf der Dienstebene Ihrer Anwendung zu haben, anstatt auf der DAO-Ebene oder gar keinem Cache.

Ihr Problem wird durch folgende Ereignisse verursacht:

Ein Artikel wird ohne seine Kategorien abgerufen und dann in Transaktion 1 in den Cache gestellt. In Transaktion 2 rufen Sie dieselbe Methode auf, rufen den Artikel ab und versuchen, seine Kategorien zu lesen. In diesem Moment versucht Hibernate, die Kategorien aus Transaktion 1 zu lesen, die dem Objekt Objekt zugeordnet ist, aber Transaktion 1 ist bereits abgeschlossen, so dass es fehlschlägt.

    
quelle