Cómo resolver la excepción de Hibernate “falló al inicializar perezosamente una colección de roles”


363

Tengo este problema

org.hibernate.LazyInitializationException: no se pudo inicializar perezosamente una colección de roles: mvc3.model.Topic.comments, no se cerró ninguna sesión o sesión

Aquí está el modelo:

@Entity
@Table(name = "T_TOPIC")
public class Topic {

    @Id
    @GeneratedValue(strategy=GenerationType.AUTO)
    private int id;

    @ManyToOne
    @JoinColumn(name="USER_ID")
    private User author;

    @Enumerated(EnumType.STRING)    
    private Tag topicTag;

    private String name;
    private String text;

    @OneToMany(mappedBy = "topic", cascade = CascadeType.ALL)
    private Collection<Comment> comments = new LinkedHashSet<Comment>();

    ...

    public Collection<Comment> getComments() {
           return comments;
    }

}

El controlador, que llama modelo, tiene el siguiente aspecto:

@Controller
@RequestMapping(value = "/topic")
public class TopicController {

    @Autowired
    private TopicService service;

    private static final Logger logger = LoggerFactory.getLogger(TopicController.class);


    @RequestMapping(value = "/details/{topicId}", method = RequestMethod.GET)
    public ModelAndView details(@PathVariable(value="topicId") int id)
    {

            Topic topicById = service.findTopicByID(id);
            Collection<Comment> commentList = topicById.getComments();

            Hashtable modelData = new Hashtable();
            modelData.put("topic", topicById);
            modelData.put("commentList", commentList);

            return new ModelAndView("/topic/details", modelData);

     }

}

La página jsp tiene el siguiente aspecto:

<%@page import="com.epam.mvc3.helpers.Utils"%>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ page session="false" %>
<html>
<head>
      <title>View Topic</title>
</head>
<body>

<ul>
<c:forEach items="${commentList}" var="item">
<jsp:useBean id="item" type="mvc3.model.Comment"/>
<li>${item.getText()}</li>

</c:forEach>
</ul>
</body>
</html>

La excepción aumenta cuando se ve jsp. En la línea con c: forEach loop

Respuestas:


214

Si sabe que querrá ver todos los Commentmensajes de correo electrónico cada vez que recupere un mensaje Topic, cambie la asignación de campo para comments:

@OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)
private Collection<Comment> comments = new LinkedHashSet<Comment>();

Las colecciones son perezosos carga por defecto, miren esto si usted quiere saber más.


35
Lo siento, pero me gustaría usar lazy-load. Entonces, he cambiado el tipo 'LinkedHashSet' t 'PersistentList'. La excepción todavía ocurre
Eugene

242
Esto podría usarse como una solución alternativa, pero no como una solución real al problema. ¿Qué pasa si necesitamos ir perezosamente?
Dkyc

14
pero en caso de que queramos perezosos, esta solución no funcionará y la mayoría de los casos solo queremos perezosos.
prashant thakre

103
Este es el tipo de respuesta que aparece en todas partes en el desbordamiento de pila. Corto, hasta el punto, resuelve el problema y ENGAÑOS. Para los futuros lectores, hágase un favor y aprenda qué es exactamente vago y ansioso, y comprenda las consecuencias.
Ced

13
@darrengorman Cuando comencé JPA publiqué una pregunta en torno a las líneas de OP. Recibí la misma respuesta que tú diste. Muy pronto, cuando realicé una prueba con cientos de miles de filas, ¿adivina qué pasó? Creo que es engañoso porque proporciona una respuesta demasiado simple para un problema que la mayoría de los principiantes enfrentarán y pronto tendrán toda su base de datos cargada en la memoria si no tienen cuidado (y no lo harán, porque no lo harán) ten en cuenta) :).
Ced

182

Desde mi experiencia, tengo los siguientes métodos para resolver la famosa LazyInitializationException:

(1) Use Hibernate.initialize

Hibernate.initialize(topics.getComments());

(2) Utilice JOIN FETCH

Puede usar la sintaxis JOIN FETCH en su JPQL para recuperar explícitamente la colección secundaria. Esto es algo así como buscar EAGER.

(3) Use OpenSessionInViewFilter

LazyInitializationException a menudo ocurre en la capa de vista. Si usa Spring Framework, puede usar OpenSessionInViewFilter. Sin embargo, no te sugiero que lo hagas. Puede conducir a problemas de rendimiento si no se usa correctamente.


55
(1) funcionó para mí perfectamente. Mi caso: Hibernate.initialize (Registry.getVehicle (). GetOwner (). GetPerson (). GetAddress ());
Leonel Sanches da Silva

66
Parece que Hibernate.initialize no funciona con EntityManager
marionmaiden

8
Esta debería ser la respuesta correcta. Por ejemplo, en mi proyecto en el trabajo, se supone que explícitamente no debemos usar la extracción EAGER. Causa problemas en este sistema en particular.
Steve Waters

Parece atractivo pero falta de documentación para implementar en otro caso ... ¿podría proporcionar más enlaces o explicaciones sobre cómo implementar esta solución?
Pipo

58

Sé que es una vieja pregunta, pero quiero ayudar. Puede poner la anotación transaccional en el método de servicio que necesita, en este caso, findTopicByID (id) debería tener

@Transactional(propagation=Propagation.REQUIRED, readOnly=true, noRollbackFor=Exception.class)

Puede encontrar más información sobre esta anotación aquí

Sobre las otras soluciones:

fetch = FetchType.EAGER 

No es una buena práctica, debe usarse SOLO si es necesario.

Hibernate.initialize(topics.getComments());

El inicializador de hibernación une sus clases a la tecnología de hibernación. Si quiere ser flexible, no es un buen camino a seguir.

Espero eso ayude


3
La anotación @Transactional funcionó para mí, pero tenga en cuenta que Propagation.REQUIRED es el valor predeterminado, al menos en Spring Boot 1.4.2 (Spring 4.3).
ben3000

44
Sí, lo es, pero pensé que podría apreciarse dejar en claro que realmente podría cambiar el
parámetro de

¿No es @Transactionaluna cosa solo de primavera?
Campa

@Campa sí lo es. Si desea manejarlo manualmente, debe poner su lógica de negocios dentro de una transacción recuperada del administrador de la entidad
sarbuLopex

54

El origen de tu problema:

Por defecto, hibernate carga perezosamente las colecciones (relaciones), lo que significa que cada vez que usa el collectioncódigo (aquí el commentscampo en la Topicclase) el hibernate lo obtiene de la base de datos, ahora el problema es que está obteniendo la colección en su controlador (donde se encuentra la sesión JPA está cerrado). Esta es la línea de código que causa la excepción (donde está cargando la commentscolección):

    Collection<Comment> commentList = topicById.getComments();

Está recibiendo una colección de "comentarios" (topic.getComments ()) en su controlador (donde JPA sessionha finalizado) y eso causa la excepción. Además, si obtuvo la commentscolección en su archivo jsp de esta manera (en lugar de obtenerla en su controlador):

<c:forEach items="topic.comments" var="item">
//some code
</c:forEach>

Aún tendría la misma excepción por el mismo motivo.

Resolviendo el problema:

Debido a que solo puede tener dos colecciones con la FetchType.Eager(colección obtenida con entusiasmo) en una clase de entidad y debido a que la carga diferida es más eficiente que la carga FetchTypeansiosa , creo que esta forma de resolver su problema es mejor que simplemente cambiar a ansioso:

Si desea inicializar la colección perezosa y también hacer que esto funcione, es mejor agregar este fragmento de código a su web.xml:

<filter>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>SpringOpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Lo que hace este código es que aumentará la longitud de su JPA sessiono, como dice la documentación, se usa, de "to allow for lazy loading in web views despite the original transactions already being completed."esta manera la sesión JPA se abrirá un poco más y, por eso, puede cargar colecciones en sus archivos jsp y clases de controlador .


77
¿Por qué se cierra la sesión JPS? ¿Cómo hacer que no se cierre? ¿Cómo realizar una colección perezosa?
atenúa el

1
¿Qué define un límite de dos colecciones FetchType.Eager por entidad?
chrisinmtown

En Spring Boot, puede agregar 'spring.jpa.open-in-view = true' a 'application.properties'
Askar el

28

La razón es que cuando usa la carga diferida, la sesión se cierra.

Hay dos soluciones

  1. No use carga perezosa.

    Establecer lazy=falseen XML o establecer @OneToMany(fetch = FetchType.EAGER)en anotación.

  2. Use carga perezosa.

    Establecer lazy=trueen XML o establecer @OneToMany(fetch = FetchType.LAZY)en anotación.

    y agregue OpenSessionInViewFilter filteren suweb.xml

Detalle Ver mi POST .


1
... y, sin embargo, ambas soluciones no son buenas. Sugerir usar EAGER puede crear grandes problemas. Usar OpenSessionInViewFilter es un antipatrón.
Rafael

27
@Controller
@RequestMapping(value = "/topic")
@Transactional

resuelvo este problema agregando @Transactional, creo que esto puede abrir la sesión


¿Por qué recibió esto un voto negativo? Agregar una transacción a la operación extiende la sesión
Tudor Grigoriu

1
Es una mala práctica agregar @Transactional al controlador.
Rafael

@Rafael ¿Por qué es una mala práctica?
Amr Ellafy

@AmrEllafy -> Aquí hay una buena explicación: stackoverflow.com/a/18498834/1261162
Rafael

22

El problema se debe al acceso a un atributo con la sesión de hibernación cerrada. No tiene una transacción de hibernación en el controlador.

Soluciones posibles:

  1. Haga toda esta lógica, en la capa de servicio , (con @Transactional), no en el controlador. Debe haber el lugar correcto para hacer esto, es parte de la lógica de la aplicación, no en el controlador (en este caso, una interfaz para cargar el modelo). Todas las operaciones en la capa de servicio deben ser transaccionales. es decir: mueva esta línea al método TopicService.findTopicByID:

    Colección commentList = topicById.getComments ();

  2. Use 'ansioso' en lugar de 'perezoso' . Ahora no está usando 'perezoso' ... no es una solución real, si quiere usar perezoso, funciona como una solución temporal (muy temporal).

  3. use @Transactional en el controlador . No debe usarse aquí, está mezclando la capa de servicio con la presentación, no es un buen diseño.
  4. use OpenSessionInViewFilter , muchas desventajas reportadas, posible inestabilidad.

En general, la mejor solución es el 1.


2
Fetch tipo de Eager supone que hibernación se retiraron todos los datos en la primera consulta, no todos los lugares es correcta
Жасулан Бердибеков

Debería MAYORAR que la MEJOR SOLUCIÓN ES 1 ... de hecho es la ÚNICA BUENA solución ya que todos los demás son antipatrones.
Rafael

19

Para cargar una colección de forma diferida, debe haber una sesión activa. En una aplicación web hay dos formas de hacer esto. Puede usar el patrón Abrir sesión en vista , donde usa un interceptor para abrir la sesión al comienzo de la solicitud y cerrarla al final. El riesgo es que debe tener un manejo sólido de excepciones o podría bloquear todas sus sesiones y su aplicación podría bloquearse.

La otra forma de manejar esto es recopilar todos los datos que necesita en su controlador, cerrar su sesión y luego ingresar los datos en su modelo. Personalmente prefiero este enfoque, ya que parece un poco más cercano al espíritu del patrón MVC. Además, si obtiene un error de la base de datos de esta manera, puede manejarlo mucho mejor que si ocurre en su renderizador de vistas. Su amigo en este escenario es Hibernate.initialize (myTopic.getComments ()). También deberá volver a adjuntar el objeto a la sesión, ya que está creando una nueva transacción con cada solicitud. Use session.lock (myTopic, LockMode.NONE) para eso.


15

Como expliqué en este artículo , la mejor manera de manejarlo LazyInitializationExceptiones buscarlo al momento de la consulta, así:

select t
from Topic t
left join fetch t.comments

SIEMPRE debe evitar los siguientes antipatrones:

Por lo tanto, asegúrese de que sus FetchType.LAZYasociaciones se inicialicen en el momento de la consulta o dentro del @Transactionalalcance original utilizando Hibernate.initializecolecciones secundarias.


1
Vlad, ¿tiene alguna sugerencia para trabajar con una colección de inicialización diferida en una entidad obtenida por el método findById () del repositorio generado por Spring? No estoy escribiendo la consulta y la transacción está fuera de mi código.
chrisinmtown

Consulte este artículo para obtener más detalles sobre la inicialización de colecciones diferidas.
Vlad Mihalcea

¿Podría aclarar lo que quiere decir con 'dentro del alcance original de @Transactional'? Esto no está claro para mí, ya que parece que obtengo este error durante una sesión abierta (¿pero no la correcta?)
Michiel Haisma

Mientras está dentro del alcance del método de servicio transaccional más destacado, también conocido como la puerta de enlace de transacciones. Mira el TrassctionInterceptorseguimiento en la pila y ese es el indicado.
Vlad Mihalcea

Una de las mejores respuestas con diferencia ... esto debería estar marcado como el correcto. Por cierto ... suponiendo que OSIV es un antipatrón, ¿cómo es posible que esté habilitado de forma predeterminada en las versiones recientes de arranque por resorte? ... tal vez no es tan malo?
Rafael

10

Si está intentando tener una relación entre una entidad y una Colección o una Lista de objetos java (por ejemplo, Tipo largo), le gustaría algo como esto:

@ElementCollection(fetch = FetchType.EAGER)
    public List<Long> ids;

1
en muchos casos, realmente no quieres hacer eso.
Pierdes

Usar EAGER no es una solución profesional.
Rafael

9

Una de las mejores soluciones es agregar lo siguiente en su archivo application.properties: spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true


1
¿Puedes decirle al OP qué hace exactamente, cualquier efecto secundario, impacto en el rendimiento?
PeS

3
En la parte posterior de la carga diferida, se bifurca una nueva sesión cada vez que se carga una asociación de forma diferida, por lo tanto, se bifurcan más conexiones y crea un poco de presión en el grupo de conexiones. Si tiene un límite en el número de conexiones, entonces esta propiedad podría no ser la correcta.
sreekmatta

2
para algunos, se considera un anti-patrón vladmihalcea.com/…
Uri Loya

7

Descubrí que declarar @PersistenceContext como EXTENDEDtambién resuelve este problema:

@PersistenceContext(type = PersistenceContextType.EXTENDED)

1
Hola, ten cuidado con tales cambios. La creación del contexto de persistencia con alcance de TRANSACTION es perezosa, que era la intención del OP. Entonces la pregunta es si quieres ser apátrida o no. Esta configuración depende del propósito del sistema y no debe cambiarse demasiado ... con entusiasmo. Si sabes a lo que me refiero. Lea aquí stackoverflow.com/questions/2547817/…
kiedysktos

Peligroso. Esta no es la respuesta correcta. Hay otros por encima mucho más precisos y seguros.
Rafael

5

fue el problema que enfrenté recientemente que resolví con el uso

<f:attribute name="collectionType" value="java.util.ArrayList" />

Descripción más detallada aquí y esto me salvó el día.


5

su lista se está cargando lentamente, por lo que la lista no se cargó. Llamar para entrar en la lista no es suficiente. use en Hibernate.initialize para iniciar la lista. Si el trabajo de dosificación se ejecuta en el elemento de la lista y llame a Hibernate.initialize para cada uno. Esto debe ser antes de regresar del alcance de la transacción. mira esta publicación
buscar -

Node n = // .. get the node
Hibernate.initialize(n); // initializes 'parent' similar to getParent.
Hibernate.initialize(n.getChildren()); // pass the lazy collection into the session 

4

Para resolver el problema en mi caso, solo faltaba esta línea

<tx:annotation-driven transaction-manager="myTxManager" />

en el archivo de contexto de la aplicación.

La @Transactionalanotación sobre un método no se tuvo en cuenta.

Espero que la respuesta ayude a alguien


4

@ Falta la anotación transaccional en el controlador

@Controller
@RequestMapping("/")
@Transactional
public class UserController {
}

17
Yo diría que la gestión de transacciones pertenece a la capa de servicio donde reside la lógica empresarial.
Sõber

No falta la anotación transaccional. El controlador no debe tener esa anotación. Esas anotaciones deben estar en el nivel de servicio.
Rafael

4

Al usar la @Transactionalanotación de hibernación , si obtiene un objeto de la base de datos con atributos recuperados de manera diferida, simplemente puede obtenerlos recuperando estos atributos de esta manera:

@Transactional
public void checkTicketSalePresence(UUID ticketUuid, UUID saleUuid) {
        Optional<Ticket> savedTicketOpt = ticketRepository.findById(ticketUuid);
        savedTicketOpt.ifPresent(ticket -> {
            Optional<Sale> saleOpt = ticket.getSales().stream().filter(sale -> sale.getUuid() == saleUuid).findFirst();
            assertThat(saleOpt).isPresent();
        });
}

Aquí, en una transacción administrada por proxy de Hibernate, el hecho de llamar ticket.getSales()hace otra consulta para obtener ventas porque lo solicitó explícitamente.


4

Dos cosas que deberías tener fetch = FetchType.LAZY.

@Transactional

y

Hibernate.initialize(topicById.getComments());

2

Para aquellos que trabajan con Criteria , encontré que

criteria.setFetchMode("lazily_fetched_member", FetchMode.EAGER);

Hice todo lo que necesitaba.

El modo de recuperación inicial para colecciones se establece en FetchMode.LAZY para proporcionar rendimiento, pero cuando necesito los datos, solo agrego esa línea y disfruto de los objetos completamente poblados.


2

En mi caso, el siguiente código fue un problema:

entityManager.detach(topicById);
topicById.getComments() // exception thrown

Porque se separó de la base de datos e Hibernate ya no recuperó la lista del campo cuando era necesario. Entonces lo inicializo antes de separar:

Hibernate.initialize(topicById.getComments());
entityManager.detach(topicById);
topicById.getComments() // works like a charm

1

La razón es que está tratando de obtener la lista de comentarios en su controlador después de cerrar la sesión dentro del servicio.

topicById.getComments();

Arriba cargará la lista de comentarios solo si su sesión de hibernación está activa, lo que supongo que cerró en su servicio.

Por lo tanto, debe obtener la Lista de comentarios antes de cerrar la sesión.


2
Sí, esta es la declaración del problema, también debe proporcionar una respuesta en unAnswer
Sarz

1

La colección commentsen su clase de modelo Topicse carga lentamente, que es el comportamiento predeterminado si no lo anota fetch = FetchType.EAGERespecíficamente.

Es muy probable que su findTopicByIDservicio esté utilizando una sesión de Hibernate sin estado. Una sesión sin estado no tiene el caché de primer nivel, es decir, no tiene contexto de persistencia. Más adelante, cuando intente iterar comments, Hibernate lanzará una excepción.

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: mvc3.model.Topic.comments, no session or session was closed

La solución puede ser:

  1. Anotar commentsconfetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
  2. Si aún desea que los comentarios se carguen de manera diferida, use las sesiones con estado de Hibernate , para que pueda obtener comentarios más tarde a pedido.


1

En mi caso, tenía el mapeo b / w Ay me Bgusta

A tiene

@OneToMany(mappedBy = "a", cascade = CascadeType.ALL)
Set<B> bs;

en la DAOcapa, el método debe ser anotado @Transactionalsi no ha anotado la asignación con Fetch Type - Eager


1

No es la mejor solución, pero para aquellos que se enfrentan LazyInitializationExceptionespecialmente en Serializationesto será de ayuda. Aquí verificará las propiedades y la configuración inicializadas de manera perezosa null. Para eso crea la clase de abajo

public class RepositoryUtil {
    public static final boolean isCollectionInitialized(Collection<?> collection) {
        if (collection instanceof PersistentCollection)
            return ((PersistentCollection) collection).wasInitialized();
        else 
            return true;
    }   
}

Dentro de su clase de entidad que tiene propiedades inicializadas perezosamente agregue un método como se muestra a continuación. Agregue todas sus propiedades de carga perezosa dentro de este método.

public void checkLazyIntialzation() {
    if (!RepositoryUtil.isCollectionInitialized(yourlazyproperty)) {
        yourlazyproperty= null;
    }

Llame a este checkLazyIntialzation()método después en todos los lugares donde está cargando datos.

 YourEntity obj= entityManager.find(YourEntity.class,1L);
  obj.checkLazyIntialzation();

0

Hola a todos, publicando bastante tarde, espero que ayude a otros, agradeciendo de antemano a @GMK por esta publicación Hibernate.initialize (objeto)

cuando perezoso = "verdadero"

Set<myObject> set=null;
hibernateSession.open
set=hibernateSession.getMyObjects();
hibernateSession.close();

ahora si accedo a 'set' después de cerrar la sesión, arroja una excepción.

Mi solución :

Set<myObject> set=new HashSet<myObject>();
hibernateSession.open
set.addAll(hibernateSession.getMyObjects());
hibernateSession.close();

ahora puedo acceder a 'set' incluso después de cerrar Hibernate Session.


0

Otra forma de hacer la cosa, puede usar TransactionTemplate para envolver la recuperación perezosa. Me gusta

Collection<Comment> commentList = this.transactionTemplate.execute
(status -> topicById.getComments());

0

El problema se debe a que el código está accediendo a una relación JPA perezosa cuando la "conexión" a la base de datos está cerrada ( el contexto de persistencia es el nombre correcto en términos de Hibernate / JPA).

Una manera simple de resolverlo en Spring Boot es definiendo una capa de servicio y usando la @Transactionalanotación. Esta anotación en un método crea una transacción que se propaga a la capa del repositorio y mantiene abierto el contexto de persistencia hasta que finaliza el método. Si accede a la colección dentro del método transaccional, Hibernate / JPA obtendrá los datos de la base de datos.

En su caso, solo necesita anotar con @Transactionalel método findTopicByID(id)en su TopicServicey forzar la búsqueda de la colección en ese método (por ejemplo, preguntando su tamaño):

    @Transactional(readOnly = true)
    public Topic findTopicById(Long id) {
        Topic topic = TopicRepository.findById(id).orElse(null);
        topic.getComments().size();
        return topic;
    }

0

Para deshacerse de la excepción de inicialización diferida, no debe solicitar la recopilación diferida cuando opera con objetos separados.

Desde mi opinión, el mejor enfoque es usar DTO, y no una entidad. En este caso, puede establecer explícitamente los campos que desea utilizar. Como siempre es suficiente. No hay que preocuparse de que algo como Jackson ObjectMapper, o hashCodegenerado por Lombok llame a sus métodos implícitamente.

Para algunos casos específicos, puede usar la @EntityGrpaphanotación, que le permite realizar la eagercarga incluso si tiene fetchType=lazyen su entidad.


0

Hay varias soluciones para este problema de inicialización diferida:

1) Cambie el tipo de Fetch de asociación de LAZY a EAGER, pero esto no es una buena práctica porque esto degradará el rendimiento.

2) Use FetchType.LAZY en el objeto asociado y también use la anotación transaccional en su método de capa de servicio para que la sesión permanezca abierta y cuando llame a topicById.getComments (), se cargará el objeto secundario (comentarios).

3) Además, intente utilizar el objeto DTO en lugar de la entidad en la capa del controlador. En su caso, la sesión se cierra en la capa del controlador. MUCHO mejor para convertir la entidad a DTO en la capa de servicio.


-11

Resolví usar List en lugar de Set:

private List<Categories> children = new ArrayList<Categories>();
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.