Rövid útmutató a hibernáláshoz enable_lazy_load_no_trans Property

1. Áttekintés

Hibernált állapotban lusta betöltés közben kivételekkel szembesülhetünk, mondván, hogy nincs munkamenet.

Ebben az oktatóanyagban megvitatjuk, hogyan lehet megoldani ezeket a lusta betöltési problémákat. Ehhez a Spring Boot segítségével felderítünk egy példát.

2. Lusta rakodási kérdések

A lusta betöltés célja az erőforrások megtakarítása azzal, hogy a fő objektum betöltésekor nem töltünk be kapcsolódó objektumokat a memóriába. Ehelyett elhalasztjuk a lusta entitások inicializálását addig a pillanatig, amikor szükség van rájuk. A hibernálás proxyt és gyűjtőcsomagolókat használ a lusta betöltéshez.

Lustán betöltött adatok lekérésekor a folyamat két lépésből áll. Először feltölti a fő objektumot, másodszor pedig az adatokat a proxybe keresi. Az adatok betöltése mindig nyitott állapotot igényel Ülés hibernált állapotban.

A probléma akkor merül fel, amikor a tranzakció lezárása után megtörténik a második lépés, ami a LazyInitializationException.

Az ajánlott megközelítés az alkalmazásunk megtervezése annak biztosítása érdekében, hogy az adatok lekérése egyetlen tranzakcióban történjen. De ez néha nehéz lehet, ha egy lusta entitást használunk a kód egy másik részében, amely nem képes megállapítani, hogy mit töltöttek be vagy nem.

A hibernálásnak van egy megoldása, egy enable_lazy_load_no_trans ingatlan. Ennek bekapcsolása azt jelenti egy lusta entitás minden lekérése ideiglenes munkamenetet nyit meg és külön tranzakción belül futtassa.

3. Lusta rakodási példa

Nézzük meg a lusta rakodás viselkedését néhány forgatókönyv szerint.

3.1 Entitások és szolgáltatások beállítása

Tegyük fel, hogy két entitásunk van, Felhasználó és Dokumentum. Egy Felhasználó sok lehet Dokumentums használjuk @Egy a sokhoz hogy leírjam azt a kapcsolatot. Továbbá használjuk @Fetch (FetchMode.SUBSELECT) a hatékonyság érdekében.

Meg kell jegyeznünk, hogy alapértelmezés szerint @Egy a sokhoz lusta fetch típusa van.

Most határozzuk meg a sajátunkat Felhasználó entitás:

@Entity public class User {// egyéb mezők el vannak hagyva a @OneToMany (mappedBy = "userId") @Fetch (FetchMode.SUBSELECT) privát lista docs = new ArrayList () rövidítése miatt. }

Ezután szükségünk van egy szolgáltatási rétegre, amely két módszerrel illusztrálja a különböző lehetőségeket. Az egyiket a következővel jegyzik @ Tranzakció. Itt mindkét módszer ugyanazt a logikát hajtja végre, ha megszámolja az összes felhasználó összes dokumentumát:

@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 (Felhasználó :: getDocs) .mapToLong (Gyűjtemény :: méret) .sum (); }}

Most nézzük meg közelebbről a következő három példát. Mi is használni fogjuk SQLStatementCountValidator megérteni a megoldás hatékonyságát a végrehajtott lekérdezések számával.

3.2. Lusta rakodás egy környező tranzakcióval

Először is használjuk a lusta betöltést az ajánlott módon. Tehát felhívjuk a miénk @ Tranzakció módszer a szolgáltatási rétegben:

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

Mint láthatjuk, ez működik és eredményt ad két kört az adatbázisba. Az első oda-vissza kiválasztja a felhasználókat, a második pedig a dokumentumaikat.

3.3. Lusta betöltés a tranzakción kívül

Hívjunk egy nem tranzakciós módszert, hogy szimuláljuk a hibát, amelyet egy környező tranzakció nélkül kapunk:

@Test (várható = LazyInitializationException.class) public void whenCallNonTransactionalMethodWithPropertyOff_thenThrowException () {serviceLayer.countAllDocsNonTransactional (); }

Ahogy jósolták, ez hibát eredményez mint a getDocs funkciója Felhasználó tranzakción kívül használják.

3.4. Lusta rakodás automatikus tranzakcióval

Ennek kijavításához engedélyezhetjük a tulajdonságot:

spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true

Bekapcsolt ingatlan mellett már nem kapunk a LazyInitializationException.

A lekérdezések száma azonban azt mutatja hat körutazás történt az adatbázisban. Itt egy oda-vissza út választja ki a felhasználókat, és öt oda-vissza út választja ki a dokumentumokat mind az öt felhasználó számára:

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

Belefutottunk a hírhedt N + 1 kérdésbe, annak ellenére, hogy egy lekérési stratégiát állítottunk be annak elkerülésére!

4. A megközelítések összehasonlítása

Beszéljünk röviden az előnyökről és hátrányokról.

A bekapcsolt ingatlan miatt nem kell aggódnunk a tranzakciók és azok határain. A hibernált kezeli ezt nekünk.

A megoldás azonban lassan működik, mert a Hibernate minden lekéréskor tranzakciót indít nekünk.

Tökéletesen működik demóknál, és amikor nem érdekelnek a teljesítményproblémák. Ez akkor lehet rendben, ha olyan gyűjtemény beolvasására szolgál, amely csak egy elemet tartalmaz, vagy egyetlen kapcsolódó objektumot egy az egyhez viszonyban.

Tulajdon nélkül a tranzakciókat részletesen ellenőrizzük, és már nem nézünk szembe teljesítményproblémákkal.

Összességében ez nem gyártásra kész funkció, és a hibernált dokumentáció figyelmeztet minket:

Bár ennek a konfigurációnak az engedélyezése lehetővé teheti LazyInitializationException menjen el, jobb, ha olyan lekérési tervet használ, amely garantálja, hogy az összes tulajdonság megfelelően inicializálódik, mielőtt a munkamenet lezárul.

5. Következtetés

Ebben az oktatóanyagban a lusta betöltéssel foglalkoztunk.

Kipróbáltunk egy hibernált tulajdonságot, hogy segítsünk a LazyInitializationException. Láttuk azt is, hogyan csökkenti a hatékonyságot, és csak korlátozott számú felhasználási esetre lehet életképes megoldás.

Mint mindig, minden kódpélda elérhető a GitHubon.