Hibernate n'a pas pu initialiser le proxy - pas de session

1. Vue d'ensemble

En travaillant avec Hibernate, nous avons peut-être rencontré une erreur qui dit: org.hibernate.LazyInitializationException: impossible d'initialiser le proxy - pas de session .

Dans ce rapide didacticiel, nous examinerons de plus près la cause première de l'erreur et apprendrons comment l'éviter.

2 Comprendre l'erreur

L'accès à un objet chargé tardivement en dehors du contexte d'une session Hibernate ouverte entraînera cette exception.

Il est important de comprendre ce que sont Session , Initialisation Lazy et Proxy Object et comment ils se combinent dans le framework Hibernate .

  • La session est un contexte de persistance qui représente une conversation entre une application et la base de données
  • Le chargement différé signifie que l'objet ne sera pas chargé dans le contexte de session jusqu'à ce qu'il soit accédé dans le code.
  • Hibernate crée une sous-classe dynamique d' objet proxy qui n'atteindra la base de données que lorsque nous utiliserons l'objet pour la première fois.

Cette erreur signifie que nous essayons d'extraire un objet chargé tardivement de la base de données en utilisant un objet proxy, mais la session Hibernate est déjà fermée.

3. Exemple pour LazyInitializationException

Voyons l'exception dans un scénario concret.

Nous voulons créer un objet Utilisateur simple avec des rôles associés. Utilisons JUnit pour démontrer l' erreur LazyInitializationException .

3.1. Classe utilitaire Hibernate

Tout d'abord, définissons une classe HibernateUtil pour créer une SessionFactory avec configuration.

Nous utiliserons la base de données HSQLDB en mémoire .

3.2. Entités

Voici notre entité utilisateur :

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

Et l' entité Role associée :

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

Comme nous pouvons le voir, il existe une relation un-à-plusieurs entre l' utilisateur et le rôle .

3.3. Création d'un utilisateur avec des rôles

Ensuite, créons deux objets Role :

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

Ensuite, nous créons un utilisateur avec les rôles:

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

Enfin, nous pouvons ouvrir une session et conserver les objets:

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

3.4. Récupération des rôles

Dans le premier scénario, nous verrons comment récupérer correctement les rôles utilisateur:

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

Ici, nous accédons à l'objet à l'intérieur de la session, il n'y a donc pas d'erreur.

3.5. Échec de la récupération des rôles

Dans le deuxième scénario, nous appellerons une méthode getRoles en dehors de la session:

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

Dans ce cas, nous essayons d'accéder aux rôles après la fermeture de la session et, par conséquent, le code lève une LazyInitializationException .

4. Comment éviter l'erreur

Jetons un coup d'œil à quatre solutions différentes pour surmonter l'erreur.

4.1. Ouvrir la session dans la couche supérieure

La meilleure pratique consiste à ouvrir une session dans la couche de persistance, par exemple à l'aide du modèle DAO.

We can open the session in the upper layers to access the associated objects in a safe manner. For example, we can open the session in the View layer.

As a result, we’ll see an increase in response time, which will affect the performance of the application.

This solution is an anti-pattern in terms of the Separation of Concerns principle. In addition, it can cause data integrity violations and long-running transactions.

4.2. Turning on enable_lazy_load_no_trans Property

This Hibernate property is used to declare a global policy for lazy-loaded object fetching.

By default, this property is false. Turning it on means that each access to an associated lazy-loaded entity will be wrapped in new session running in a new transaction:

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

Dans cet article, nous avons vu comment gérer l' exception org.hibernate.LazyInitializationException: impossible d'initialiser le proxy - pas d' erreur de session .

Nous avons exploré différentes approches ainsi que des problèmes de performance. Il est important d'utiliser une solution simple et efficace pour éviter d'affecter les performances.

Enfin, nous avons vu en quoi l'approche de récupération de jointure est un bon moyen d'éviter l'erreur.

Comme toujours, le code est disponible sur sur GitHub.