Hibernate no pudo inicializar el proxy - no hay sesión

1. Información general

Trabajando con Hibernate, es posible que hayamos encontrado un error que dice: org.hibernate.LazyInitializationException: no se pudo inicializar el proxy - no hay sesión .

En este tutorial rápido, analizaremos más de cerca la causa raíz del error y aprenderemos cómo evitarlo.

2 Comprensión del error

El acceso a un objeto de carga diferida fuera del contexto de una sesión abierta de Hibernate resultará en esta excepción.

Es importante comprender qué es Session , Lazy Initialisation y Proxy Object y cómo se combinan en el marco de Hibernate .

  • La sesión es un contexto de persistencia que representa una conversación entre una aplicación y la base de datos.
  • Carga diferida significa que el objeto no se cargará en el contexto de la sesión hasta que se acceda a él en el código.
  • Hibernate crea una subclase de Objeto Proxy dinámico que llegará a la base de datos solo cuando usemos el objeto por primera vez.

Este error significa que intentamos recuperar un objeto de carga diferida de la base de datos utilizando un objeto proxy, pero la sesión de Hibernate ya está cerrada.

3. Ejemplo de LazyInitializationException

Veamos la excepción en un escenario concreto.

Queremos crear un objeto de usuario simple con roles asociados. Usemos JUnit para demostrar el error LazyInitializationException .

3.1. Clase de utilidad de hibernación

Primero, definamos una clase HibernateUtil para crear una SessionFactory con configuración.

Usaremos la base de datos HSQLDB en memoria .

3.2. Entidades

Aquí está nuestra entidad de usuario :

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

Y la entidad Role asociada :

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

Como podemos ver, existe una relación de uno a varios entre Usuario y Rol .

3.3. Crear usuario con roles

A continuación, creemos dos objetos de rol :

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

Luego, creamos un Usuario con los roles:

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

Finalmente, podemos abrir una sesión y conservar los objetos:

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

3.4. Obteniendo roles

En el primer escenario, veremos cómo obtener roles de usuario de manera adecuada:

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

Aquí accedemos al objeto dentro de la sesión, por lo que no hay error.

3.5. Error de obtención de roles

En el segundo escenario, llamaremos a un método getRoles fuera de la sesión:

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

En ese caso, intentamos acceder a los roles después de que se cerró la sesión y, como resultado, el código arroja una LazyInitializationException .

4. Cómo evitar el error

Echemos un vistazo a cuatro soluciones diferentes para superar el error.

4.1. Sesión abierta en la capa superior

La mejor práctica es abrir una sesión en la capa de persistencia, por ejemplo, utilizando el patrón DAO.

Podemos abrir la sesión en las capas superiores para acceder a los objetos asociados de forma segura. Por ejemplo, podemos abrir la sesión en la capa Ver .

Como resultado, veremos un aumento en el tiempo de respuesta, lo que afectará el rendimiento de la aplicación.

Esta solución es un anti-patrón en términos del principio de separación de preocupaciones. Además, puede provocar violaciones de la integridad de los datos y transacciones de larga duración.

4.2. Activando la propiedad enable_lazy_load_no_trans

Esta propiedad de Hibernate se utiliza para declarar una política global para la recuperación de objetos con carga diferida.

De forma predeterminada, esta propiedad es falsa . Activarlo significa que cada acceso a una entidad de carga diferida asociada se incluirá en una nueva sesión que se ejecuta en una nueva transacción:

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

En este artículo, vimos cómo lidiar con la excepción org.hibernate.LazyInitializationException: no se pudo inicializar el proxy, no hay error de sesión .

Exploramos diferentes enfoques junto con problemas de rendimiento. Es importante utilizar una solución simple y eficiente para evitar afectar el rendimiento.

Finalmente, vimos cómo el enfoque de búsqueda de combinaciones es una buena manera de evitar el error.

Como siempre, el código está disponible en GitHub.