Viloläge kunde inte initiera proxy - ingen session

1. Översikt

När vi arbetar med Hibernate kan vi ha stött på ett fel som säger: org.hibernate.LazyInitializationException: kunde inte initiera proxy - ingen session .

I den här snabba handledningen tar vi en närmare titt på orsaken till felet och lär oss hur man undviker det.

2 Förstå felet

Åtkomst till ett laddat objekt utanför sammanhanget för en öppen viloläge kommer att resultera i detta undantag.

Det är viktigt att förstå vad som är Session , Lazy Initialisation och Proxy Object och hur de samlas i viloläge .

  • Session är ett uthållighetskontext som representerar en konversation mellan en applikation och databasen
  • Lazy Loading betyder att objektet inte laddas till sessionens sammanhang förrän det nås i kod.
  • Hibernate skapar en dynamisk Proxy Object- underklass som bara kommer att träffa databasen när vi först använder objektet.

Det här felet innebär att vi försöker hämta ett latsamt objekt från databasen med hjälp av ett proxyobjekt, men viloläge-sessionen är redan stängd.

3. Exempel för LazyInitializationException

Låt oss se undantaget i ett konkret scenario.

Vi vill skapa en enkel användarobjekt med tillhörande roller. Låt oss använda JUnit för att visa LazyInitializationException- felet.

3.1. Hibernate Utility Class

Låt oss först definiera en HibernateUtil- klass för att skapa en SessionFactory med konfiguration.

Vi använder HSQLDB- databasen i minnet .

3.2. Enheter

Här är vår Användar enhet:

@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; } 

Och tillhörande Roll enhet:

@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; }

Som vi kan se finns det en en-till-många relation mellan användare och roll .

3.3. Skapa användare med roller

Låt oss sedan skapa två rollobjekt :

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

Sedan skapar vi en användare med rollerna:

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

Slutligen kan vi öppna en session och bestå objekten:

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

3.4. Hämtar roller

I det första scenariot ser vi hur man hämtar användarroller på ett korrekt sätt:

@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(); }

Här kommer vi åt objektet inuti sessionen, därför finns det inget fel.

3.5. Hämtar roller misslyckande

I det andra scenariot kommer vi att kalla en getRoles- metod utanför sessionen:

@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()); }

I så fall försöker vi komma åt rollerna efter att sessionen stängdes, och som ett resultat kastar koden ett LazyInitializationException .

4. Hur man undviker felet

Låt oss ta en titt på fyra olika lösningar för att övervinna felet.

4.1. Öppna session i övre lager

Den bästa metoden är att öppna en session i uthållighetsskiktet, till exempel med hjälp av DAO-mönstret.

Vi kan öppna sessionen i de övre lagren för att komma åt de associerade objekten på ett säkert sätt. Vi kan till exempel öppna sessionen i Visa- lagret.

Som ett resultat ser vi en ökad svarstid, vilket kommer att påverka applikationens prestanda.

Denna lösning är ett antimönster när det gäller principen om separering av bekymmer. Dessutom kan det orsaka kränkningar av dataintegritet och långvariga transaktioner.

4.2. Slå på enable_lazy_load_no_trans Property

Den här viloläge-egenskapen används för att deklarera en global policy för hämtning av lat laddade objekt.

Som standard är den här egenskapen falsk . Att slå på det betyder att varje åtkomst till en associerad lat laddad enhet kommer att slås in i en ny session som körs i en ny transaktion:

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

I den här artikeln, såg vi hur man handskas med org.hibernate.LazyInitializationException: Kunde inte initiera proxy - ingen Session fel .

Vi utforskade olika tillvägagångssätt tillsammans med prestandafrågor. Det är viktigt att använda en enkel och effektiv lösning för att undvika att påverka prestanda.

Slutligen såg vi hur metoden med att hämta är ett bra sätt att undvika felet.

Som alltid finns koden tillgänglig på GitHub.