Hibernate nu a putut inițializa proxy - fără sesiune

1. Prezentare generală

Lucrând cu Hibernate, este posibil să fi întâlnit o eroare care să spună: org.hibernate.LazyInitializationException: nu s-a putut inițializa proxy - nu există sesiune .

În acest tutorial rapid, vom analiza mai atent cauza principală a erorii și vom învăța cum să o evităm.

2 Înțelegerea erorii

Accesul la un obiect încărcat leneș în afara contextului unei sesiuni deschise de hibernare va duce la această excepție.

Este important să înțelegeți ce este sesiunea , inițializarea leneșă și obiectul proxy și cum se reunesc în cadrul Hibernate .

  • Sesiunea este un context de persistență care reprezintă o conversație între o aplicație și baza de date
  • Încărcarea leneșă înseamnă că obiectul nu va fi încărcat în contextul sesiunii până când nu va fi accesat în cod.
  • Hibernate creează o subclasă dinamic Obiect proxy care va atinge baza de date numai atunci când vom folosi pentru prima dată obiectul.

Această eroare înseamnă că încercăm să preluăm un obiect încărcat leneș din baza de date folosind un obiect proxy, dar sesiunea de hibernare este deja închisă.

3. Exemplu pentru LazyInitializationException

Să vedem excepția într-un scenariu concret.

Vrem să creăm un obiect utilizator simplu cu roluri asociate. Să folosim JUnit pentru a demonstra eroarea LazyInitializationException .

3.1. Clasa de utilitate Hibernate

În primul rând, să definim o clasă HibernateUtil pentru a crea o SessionFactory cu configurație.

Vom folosi baza de date HSQLDB în memorie .

3.2. Entități

Iată entitatea noastră de utilizator :

@Entity @Table(name = "user") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private int id; @Column(name = "first_name") private String firstName; @Column(name = "last_name") private String lastName; @OneToMany private Set roles; } 

Și entitatea de rol asociată :

@Entity @Table(name = "role") public class Role { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") private int id; @Column(name = "role_name") private String roleName; }

După cum putem vedea, există o relație unu-la-mulți între utilizator și rol .

3.3. Crearea utilizatorului cu roluri

Apoi, să creăm două obiecte de rol :

Role admin = new Role("Admin"); Role dba = new Role("DBA");

Apoi, creăm un utilizator cu rolurile:

User user = new User("Bob", "Smith"); user.addRole(admin); user.addRole(dba);

În cele din urmă, putem deschide o sesiune și putem persista obiectele:

Session session = sessionFactory.openSession(); session.beginTransaction(); user.getRoles().forEach(role -> session.save(role)); session.save(user); session.getTransaction().commit(); session.close();

3.4. Preluarea rolurilor

În primul scenariu, vom vedea cum să preluăm rolurile utilizatorilor într-un mod adecvat:

@Test public void whenAccessUserRolesInsideSession_thenSuccess() { User detachedUser = createUserWithRoles(); Session session = sessionFactory.openSession(); session.beginTransaction(); User persistentUser = session.find(User.class, detachedUser.getId()); Assert.assertEquals(2, persistentUser.getRoles().size()); session.getTransaction().commit(); session.close(); }

Aici, accesăm obiectul din interiorul sesiunii, prin urmare nu există nicio eroare.

3.5. Preluarea rolurilor eșec

În al doilea scenariu, vom apela o metodă getRoles în afara sesiunii:

@Test public void whenAccessUserRolesOutsideSession_thenThrownException() { User detachedUser = createUserWithRoles(); Session session = sessionFactory.openSession(); session.beginTransaction(); User persistentUser = session.find(User.class, detachedUser.getId()); session.getTransaction().commit(); session.close(); thrown.expect(LazyInitializationException.class); System.out.println(persistentUser.getRoles().size()); }

În acest caz, încercăm să accesăm rolurile după închiderea sesiunii și, ca rezultat, codul aruncă o excepție LazyInitializationException .

4. Cum să evitați eroarea

Să aruncăm o privire la patru soluții diferite pentru a depăși eroarea.

4.1. Deschideți sesiunea în stratul superior

Cea mai bună practică este deschiderea unei sesiuni în stratul de persistență, de exemplu folosind modelul DAO.

Putem deschide sesiunea în straturile superioare pentru a accesa obiectele asociate într-un mod sigur. De exemplu, putem deschide sesiunea în stratul View .

Ca urmare, vom vedea o creștere a timpului de răspuns, care va afecta performanța aplicației.

Această soluție este un model anti-model în ceea ce privește principiul Separarea preocupărilor. În plus, poate provoca încălcări ale integrității datelor și tranzacții pe termen lung.

4.2. Activarea proprietății enable_lazy_load_no_trans

Această proprietate Hibernate este utilizată pentru a declara o politică globală pentru preluarea obiectelor încărcate leneș.

În mod implicit, această proprietate este falsă . Activarea acestuia înseamnă că fiecare acces la o entitate asociată leneș-încărcată va fi înfășurat într-o nouă sesiune care rulează într-o nouă tranzacție:

Using this property to avoid LazyInitializationException error is not recommended since it will slow down the performance of our application. This is because we'll end up with an n + 1 problem. Simply put, that means one SELECT for the User and N additional SELECTs to fetch the roles of each user.

This approach is not efficient and also considered an anti-pattern.

4.3. Using FetchType.EAGER Strategy

We can use this strategy along with a @OneToMany annotation, for example :

@OneToMany(fetch = FetchType.EAGER) @JoinColumn(name = "user_id") private Set roles;

This is a kind of compromised solution for a particular usage when we need to fetch the associated collection for most of our use cases.

So it's much easier to declare the EAGER fetch type instead of explicitly fetching the collection for most of the different business flows.

4.4. Using Join Fetching

We can use a JOIN FETCH directive in JPQL to fetch the associated collection on-demand, for example :

SELECT u FROM User u JOIN FETCH u.roles

Or we can use the Hibernate Criteria API :

Criteria criteria = session.createCriteria(User.class); criteria.setFetchMode("roles", FetchMode.EAGER);

Here, we specify the associated collection that should be fetched from the database along with the User object on the same round trip. Using this query improves the efficiency of iteration since it eliminates the need for retrieving the associated objects separately.

This is the most efficient and fine-grained solution to avoid the LazyInitializationException error.

5. Conclusion

În acest articol, am văzut cum să facem față org.hibernate.LazyInitializationException: nu s-a putut inițializa proxy - nu există eroare de sesiune .

Am explorat diferite abordări împreună cu probleme de performanță. Este important să utilizați o soluție simplă și eficientă pentru a evita afectarea performanței.

În cele din urmă, am văzut cum abordarea prin prelucrare prin îmbinare este o modalitate bună de a evita eroarea.

Ca întotdeauna, codul este disponibil pe GitHub.