A hibernálás nem tudta inicializálni a proxyt - nincs munkamenet

1. Áttekintés

A hibernált rendszerrel együttműködve előfordulhat, hogy hibát észleltünk: org.hibernate.LazyInitializationException: nem lehet inicializálni a proxyt - nincs munkamenet.

Ebben a gyors bemutatóban közelebbről megvizsgáljuk a hiba kiváltó okát, és megtanuljuk, hogyan kerülhetjük el azt.

2 A hiba megértése

A lustán letöltött objektumokhoz való hozzáférés a nyitott hibernált munkamenet környezetén kívül eredményezi ezt a kivételt.

Fontos megérteni mi az a Session, Lusta inicializálás,és Proxy Object és hogyan jönnek össze a Hibernálás keretrendszer.

  • Ülés olyan perzisztencia-kontextus, amely egy alkalmazás és az adatbázis közötti beszélgetést ábrázolja
  • Lusta betöltés azt jelenti, hogy az objektum nem lesz betöltve a Ülés kontextusban, amíg hozzá nem férnek hozzá kódban.
  • A hibernálás dinamikát hoz létre Proxy Object alosztály, amely csak akkor éri el az adatbázist, amikor először használjuk az objektumot.

Ez a hiba azt jelenti, hogy egy lusta módon feltöltött objektumot proxyobjektummal próbálunk lekérni, de a Hibernate munkamenet már le van zárva.

3. Példa erre LazyInitializationException

Nézzük meg a kivételt egy konkrét forgatókönyvben.

Egyszerűt akarunk létrehozni Felhasználó objektum társított szerepekkel. Használjuk a JUnit-et a LazyInitializationException hiba.

3.1. Hibernált segédprogram

Először definiáljuk a HibernateUtil osztály létrehozásához SessionFactory konfigurációval.

Használjuk a memóriát HSQLDB adatbázis.

3.2. Entitások

Itt van a miénk Felhasználó entitás:

@Entity @Table (name = "user") public class User {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) @Column (name = "id") private int id; @ Oszlop (név = "keresztnév") privát karakterlánc keresztnév; @ Oszlop (név = "vezetéknév") privát karakterlánc vezetéknév; @OneToMany private Set szerepkörök; } 

És a kapcsolódó Szerep entitás:

@Entity @Table (name = "role") public class Role {@Id @GeneratedValue (strategy = GenerationType.IDENTITY) @Column (name = "id") private int id; @ Oszlop (név = "szerep_név") privát karakterlánc szerepNév; }

Mint láthatjuk, van egy a sokhoz viszony Felhasználó és Szerep.

3.3. Felhasználó létrehozása szerepkörökkel

Ezután hozzunk létre kettőt Szerep tárgyak:

Role admin = new Role ("Rendszergazda"); Szerep: dba = új szerep ("DBA");

Ezután létrehozunk egy Felhasználó szerepekkel:

Felhasználó felhasználó = új felhasználó ("Bob", "Smith"); user.addRole (admin); user.addRole (dba);

Végül megnyithatunk egy munkamenetet és megtarthatjuk az objektumokat:

Munkamenet = sessionFactory.openSession (); session.beginTransaction (); user.getRoles (). forEach (szerepkör -> session.save (szerep)); session.save (felhasználó); session.getTransaction (). kötelezettség (); session.close ();

3.4. Szerepek beolvasása

Az első esetben meglátjuk, hogyan lehet megfelelő módon lekérni a felhasználói szerepeket:

@Test public void whenAccessUserRolesInsideSession_thenSuccess () {User detachedUser = createUserWithRoles (); Munkamenet = sessionFactory.openSession (); session.beginTransaction (); User persistentUser = session.find (Felhasználó.osztály, detachedUser.getId ()); Assert.assertEquals (2, persistentUser.getRoles (). Size ()); session.getTransaction (). kötelezettség (); session.close (); }

Itt elérjük az objektumot a munkameneten belül, ezért nincs hiba.

3.5. Szerepek meghiúsulása

A második forgatókönyvben a-t hívjuk getRoles módszer a munkameneten kívül:

@Test public void whenAccessUserRolesOutsideSession_thenThrownException () {User detachedUser = createUserWithRoles (); Munkamenet = sessionFactory.openSession (); session.beginTransaction (); User persistentUser = session.find (Felhasználó.osztály, detachedUser.getId ()); session.getTransaction (). kötelezettség (); session.close (); thrown.expect (LazyInitializationException.class); System.out.println (persistentUser.getRoles (). Size ()); }

Ebben az esetben megpróbálunk hozzáférni a szerepkörökhöz a munkamenet lezárása után, és ennek eredményeként a kód dob egy LazyInitializationException.

4. Hogyan lehet elkerülni a hibát

Vizsgáljunk meg négy különböző megoldást a hiba leküzdésére.

4.1. Nyitott munkamenet a felső rétegben

A legjobb gyakorlat egy munkamenet megnyitása a perzisztencia rétegben, például a DAO minta használatával.

Meg tudjuk nyitni a munkamenetet a felső rétegekben, hogy biztonságos módon hozzáférjünk a társított objektumokhoz. Megnyithatjuk például a munkamenetet a Kilátás réteg.

Ennek eredményeként megnő a válaszidő, ami hatással lesz az alkalmazás teljesítményére.

Ez a megoldás antiminta az aggodalmak elkülönítésének elvét tekintve. Ezenkívül az adatok integritásának megsértését és hosszan tartó tranzakciókat is okozhat.

4.2. Bekapcsolni enable_lazy_load_no_trans Ingatlan

Ez a hibernált tulajdonság a lusta betöltött objektumok lekérésére vonatkozó globális házirend deklarálására szolgál.

Alapértelmezés szerint ez a tulajdonság hamis. Bekapcsolása azt jelenti, hogy a társított, lustán töltött entitásokhoz való minden hozzáférés új munkamenetbe kerül, amely új tranzakcióban fut:

Ennek a tulajdonságnak az elkerülése érdekében LazyInitializationException hiba nem ajánlott, mert ez lelassítja alkalmazásunk teljesítményét. Ez azért van, mert megtesszük végül n + 1 problémával jár. Egyszerűen fogalmazva, ez azt jelenti, hogy egy SELECT a Felhasználó és N további SELECTS az egyes felhasználók szerepeinek lekéréséhez.

Ez a megközelítés nem hatékony, és anti-mintának is tekinthető.

4.3. Használata FetchType.EAGER Stratégia

Használhatjuk ezt a stratégiát az a-val együtt @Egy a sokhoz kommentár, például:

@OneToMany (fetch = FetchType.EAGER) @JoinColumn (name = "user_id") privát Szerepkörök beállítása;

Ez egyfajta veszélyeztetett megoldás egy adott felhasználásra, amikor a legtöbb használati esetre be kell szereznünk a társított gyűjteményt.

Tehát sokkal könnyebb kijelenteni a MOHÓ letöltési típus ahelyett, hogy a különféle üzleti folyamatok többségére kifejezetten lekérné a gyűjteményt.

4.4. A Csatlakozás lehívása használatával

Használhatjuk a CSATLAKOZZA FETCH irányelv JPQL igény szerint lekérheti a társított gyűjteményt, például:

KIVÁLASZTÁS UTÓL a felhasználótól u CSATLAKOZZ FETCH u.szerepek

Vagy használhatjuk a Hibernate Criteria API-t:

Feltételek kritériumai = session.createCriteria (Felhasználó.osztály); feltételek.setFetchMode ("szerepkörök", FetchMode.EAGER);

Itt adjuk meg a társított gyűjteményt, amelyet be kell tölteni az adatbázisból, a Felhasználó objektum ugyanazon a körúton. A lekérdezés használata javítja az iteráció hatékonyságát, mivel így nincs szükség a társított objektumok külön-külön történő letöltésére.

Ez a leghatékonyabb és finom szemcseméretű megoldás a LazyInitializationException hiba.

5. Következtetés

Ebben a cikkben láttuk, hogyan kell kezelni a org.hibernate.LazyInitializationException: nem lehet inicializálni a proxyt - nincs munkamenet hiba.

Különböző megközelítéseket tártunk fel a teljesítményproblémákkal együtt. Fontos, hogy egyszerű és hatékony megoldást alkalmazzon a teljesítmény befolyásolásának elkerülése érdekében.

Végül láttuk, hogy a csatlakozás-lehívás megközelítés jó módszer a hibák elkerülésére.

Mint mindig, a kód elérhető a GitHubon.