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.