Kurzanleitung zum Ruhezustand enable_lazy_load_no_trans-Eigenschaft

1. Übersicht

Bei der Verwendung des verzögerten Ladens im Ruhezustand können Ausnahmen auftreten, da keine Sitzung vorhanden ist.

In diesem Tutorial werden wir diskutieren, wie Sie diese Probleme beim verzögerten Laden lösen können. Dazu verwenden wir Spring Boot, um ein Beispiel zu untersuchen.

2. Probleme beim langsamen Laden

Das Ziel des verzögerten Ladens besteht darin, Ressourcen zu sparen, indem verwandte Objekte beim Laden des Hauptobjekts nicht in den Speicher geladen werden. Stattdessen verschieben wir die Initialisierung von faulen Entitäten auf den Moment, in dem sie benötigt werden. Hibernate verwendet Proxys und Collection Wrapper, um das verzögerte Laden zu implementieren.

Beim Abrufen von träge geladenen Daten sind zwei Schritte erforderlich. Erstens wird das Hauptobjekt gefüllt und zweitens werden die Daten in seinen Proxys abgerufen. Das Laden von Daten erfordert immer eine offene Sitzung im Ruhezustand.

Das Problem tritt auf, wenn der zweite Schritt nach dem Abschluss der Transaktion erfolgt , was zu einer LazyInitializationException führt .

Der empfohlene Ansatz besteht darin, unsere Anwendung so zu gestalten, dass der Datenabruf in einer einzigen Transaktion erfolgt. Dies kann jedoch manchmal schwierig sein, wenn eine faule Entität in einem anderen Teil des Codes verwendet wird, der nicht feststellen kann, was geladen wurde oder nicht.

Der Ruhezustand hat eine Problemumgehung , eine Eigenschaft enable_lazy_load_no_trans . Wenn Sie diese Option aktivieren, wird bei jedem Abruf einer faulen Entität eine temporäre Sitzung geöffnet und in einer separaten Transaktion ausgeführt.

3. Lazy Loading Beispiel

Schauen wir uns das Verhalten des verzögerten Ladens in einigen Szenarien an.

3.1 Entitäten und Dienste einrichten

Angenommen, wir haben zwei Entitäten, Benutzer und Dokument . Ein Benutzer kann viele Dokumente haben , und wir werden @OneToMany verwenden , um diese Beziehung zu beschreiben. Außerdem verwenden wir aus Effizienzgründen @Fetch (FetchMode.SUBSELECT) .

Wir sollten beachten, dass @OneToMany standardmäßig einen Lazy-Fetch-Typ hat.

Definieren wir nun unsere Benutzerentität :

@Entity public class User { // other fields are omitted for brevity @OneToMany(mappedBy = "userId") @Fetch(FetchMode.SUBSELECT) private List docs = new ArrayList(); }

Als nächstes benötigen wir eine Service-Schicht mit zwei Methoden, um die verschiedenen Optionen zu veranschaulichen. Einer von ihnen ist mit @Transactional versehen . Hier führen beide Methoden dieselbe Logik aus, indem sie alle Dokumente aller Benutzer zählen:

@Service public class ServiceLayer { @Autowired private UserRepository userRepository; @Transactional(readOnly = true) public long countAllDocsTransactional() { return countAllDocs(); } public long countAllDocsNonTransactional() { return countAllDocs(); } private long countAllDocs() { return userRepository.findAll() .stream() .map(User::getDocs) .mapToLong(Collection::size) .sum(); } }

Schauen wir uns nun die folgenden drei Beispiele genauer an. Wir werden auch SQLStatementCountValidator verwenden , um die Effizienz der Lösung zu verstehen, indem wir die Anzahl der ausgeführten Abfragen zählen.

3.2. Faules Laden mit einer umgebenden Transaktion

Lassen Sie uns zunächst das verzögerte Laden auf die empfohlene Weise verwenden. Also rufen wir unsere @ Transactional- Methode in der Service-Schicht auf:

@Test public void whenCallTransactionalMethodWithPropertyOff_thenTestPass() { SQLStatementCountValidator.reset(); long docsCount = serviceLayer.countAllDocsTransactional(); assertEquals(EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount(2); }

Wie wir sehen können, funktioniert dies und führt zu zwei Roundtrips zur Datenbank . Der erste Roundtrip wählt Benutzer aus und der zweite wählt ihre Dokumente aus.

3.3. Faules Laden außerhalb einer Transaktion

Rufen wir nun eine nicht-transaktionale Methode auf, um den Fehler zu simulieren, den wir ohne eine umgebende Transaktion erhalten:

@Test(expected = LazyInitializationException.class) public void whenCallNonTransactionalMethodWithPropertyOff_thenThrowException() { serviceLayer.countAllDocsNonTransactional(); }

Wie vorhergesagt, führt dies zu einem Fehler, da die getDocs- Funktion des Benutzers außerhalb einer Transaktion verwendet wird.

3.4. Lazy Loading mit automatischer Transaktion

Um dies zu beheben, können wir die Eigenschaft aktivieren:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true

Wenn die Eigenschaft aktiviert ist , erhalten wir keine LazyInitializationException mehr .

Die Anzahl der Abfragen zeigt jedoch, dass sechs Roundtrips zur Datenbank durchgeführt wurden . Hier wählt eine Hin- und Rückfahrt Benutzer aus, und fünf Hin- und Rückfahrten wählen Dokumente für jeden von fünf Benutzern aus:

@Test public void whenCallNonTransactionalMethodWithPropertyOn_thenGetNplusOne() { SQLStatementCountValidator.reset(); long docsCount = serviceLayer.countAllDocsNonTransactional(); assertEquals(EXPECTED_DOCS_COLLECTION_SIZE, docsCount); SQLStatementCountValidator.assertSelectCount(EXPECTED_USERS_COUNT + 1); }

Wir sind auf das berüchtigte N + 1-Problem gestoßen , obwohl wir eine Abrufstrategie festgelegt haben, um dies zu vermeiden!

4. Vergleich der Ansätze

Lassen Sie uns kurz die Vor- und Nachteile diskutieren.

Wenn die Eigenschaft aktiviert ist, müssen wir uns nicht um Transaktionen und deren Grenzen kümmern. Hibernate schafft das für uns.

Die Lösung funktioniert jedoch langsam, da Hibernate bei jedem Abruf eine Transaktion für uns startet.

Es funktioniert perfekt für Demos und wenn wir uns nicht um Leistungsprobleme kümmern. Dies kann in Ordnung sein, wenn eine Sammlung abgerufen wird, die nur ein Element enthält, oder ein einzelnes verwandtes Objekt in einer Eins-zu-Eins-Beziehung.

Ohne die Immobilie haben wir eine genaue Kontrolle über die Transaktionen und haben keine Leistungsprobleme mehr.

Insgesamt ist dies keine produktionsbereite Funktion , und die Hibernate-Dokumentation warnt uns:

Durch Aktivieren dieser Konfiguration kann LazyInitializationException möglicherweise nicht mehr verwendet werden. Es ist jedoch besser, einen Abrufplan zu verwenden, der garantiert, dass alle Eigenschaften ordnungsgemäß initialisiert werden, bevor die Sitzung geschlossen wird.

5. Schlussfolgerung

In diesem Tutorial haben wir uns mit dem verzögerten Laden befasst.

Wir haben versucht, eine Hibernate-Eigenschaft zu verwenden, um die LazyInitializationException zu überwinden . Wir haben auch gesehen, wie es die Effizienz verringert und möglicherweise nur für eine begrenzte Anzahl von Anwendungsfällen eine praktikable Lösung darstellt.

Wie immer sind alle Codebeispiele auf GitHub verfügbar.