Como resolver a exceção "Hibernate" falhou ao inicializar preguiçosamente uma coleção de funções


363

Eu tenho esse problema:

org.hibernate.LazyInitializationException: falha ao inicializar preguiçosamente uma coleção de funções: mvc3.model.Topic.comments, nenhuma sessão ou sessão foi fechada

Aqui está o 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;
    }

}

O controlador, que chama modelo, se parece com o seguinte:

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

     }

}

A página jsp parece com o seguinte:

<%@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>

A exceção é aumentada ao visualizar jsp. Na linha com loop c: forEach

Respostas:


214

Se você sabe que deseja ver todos os Comments sempre que recuperar um Topic, altere seu mapeamento de campo para comments:

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

As coleções são carregadas preguiçosamente por padrão, dê uma olhada nisso se você quiser saber mais.


35
Desculpe, mas eu gostaria de usar o lazy-load. Então, mudei o tipo 'LinkedHashSet' para 'PersistentList'. Exceção ainda ocorre
Eugene

242
Isso pode ser usado como uma solução alternativa, mas não como uma solução real para o problema. E se precisarmos buscar preguiçosamente?
Dkyc

14
mas, se quisermos preguiçoso, essa solução não funcionará e, na maioria dos casos, queremos apenas preguiçoso.
Prashant thakre

103
Esse é o tipo de resposta que aparece em todo lugar no estouro da pilha. Curto, direto ao ponto, resolve o problema e engana. Para futuros leitores, faça um favor a si mesmo e aprenda o que exatamente é preguiçoso e buscado com ansiedade, e entenda as consequências.
Ced

13
@darrengorman Quando iniciei o JPA, postei uma pergunta semelhante à do OP. Recebi a mesma resposta que você deu. Logo, quando eu fiz alguns testes com centenas de milhares de linhas, adivinhe o que aconteceu? Eu acho que é enganoso, porque fornece uma resposta muito simples para um problema que a maioria dos iniciantes enfrentará e, em breve, eles terão todo o banco de dados carregado na memória se não tomarem cuidado (e não o farão, porque não o farão). esteja ciente disso) :).
Ced

182

Da minha experiência, tenho os seguintes métodos para resolver a famosa LazyInitializationException:

(1) Use Hibernate.initialize

Hibernate.initialize(topics.getComments());

(2) Use JOIN FETCH

Você pode usar a sintaxe JOIN FETCH no seu JPQL para buscar explicitamente a coleção filho. É assim que o EAGER busca.

(3) Use OpenSessionInViewFilter

LazyInitializationException geralmente ocorre na camada de exibição. Se você usa a estrutura Spring, pode usar o OpenSessionInViewFilter. No entanto, eu não sugiro que você faça isso. Isso pode levar a um problema de desempenho se não for usado corretamente.


5
(1) funcionou para mim perfeitamente. Meu caso: Hibernate.initialize (registry.getVehicle (). GetOwner (). GetPerson (). GetAddress ());
Leonel Sanches da Silva

6
Parece que Hibernate.initialize não funciona com EntityManager
marionmaiden

8
Essa deve ser a resposta correta. Por exemplo, no meu projeto no trabalho, não devemos explicitamente usar a busca pelo EAGER. Causa problemas neste sistema em particular.
23817 Steve Waters

Parece atraente, mas falta de documentação para implementar em outro caso ... você poderia fornecer mais alguns links ou explicações sobre como implementar esta solução?
Pipo

58

Sei que é uma pergunta antiga, mas quero ajudar. Você pode colocar a anotação transacional no método de serviço necessário, neste caso, findTopicByID (id) deve ter

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

mais informações sobre esta anotação podem ser encontradas aqui

Sobre as outras soluções:

fetch = FetchType.EAGER 

não é uma boa prática, deve ser usada SOMENTE se necessário.

Hibernate.initialize(topics.getComments());

O inicializador de hibernação vincula suas classes à tecnologia de hibernação. Se você pretende ser flexível, não é um bom caminho a percorrer.

Espero que ajude


3
A anotação @Transactional funcionou para mim, mas observe que Propagation.REQUIRED é o padrão, pelo menos no Spring Boot 1.4.2 (Spring 4.3).
ben3000

4
Sim, é, mas eu pensei que poderia ser apreciada para deixar claro que você realmente pode mudar o parâmetro de propagação
sarbuLopex

Não é @Transactionaluma coisa apenas de primavera?
Campa

@Cam sim, é. Se você quer lidar com isso manualmente você deve colocar sua lógica de negócio dentro de uma transação recuperada a partir do gerenciador de entidade
sarbuLopex

54

A origem do seu problema:

Por padrão, o hibernate carrega preguiçosamente as coleções (relacionamentos), o que significa que, sempre que você usa collectionno seu código ( commentscampo aqui na Topicclasse), o hibernate obtém isso do banco de dados, agora o problema é que você está obtendo a coleção no seu controlador (onde a sessão JPA esta é a linha de código que causa a exceção (onde você está carregando a commentscoleção):

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

Você está recebendo a coleção de "comentários" (topic.getComments ()) em seu controlador (onde JPA sessionterminou) e isso causa a exceção. Além disso, se você obteve a commentscoleção no seu arquivo jsp assim (em vez de obtê-la no seu controlador):

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

Você ainda teria a mesma exceção pelo mesmo motivo.

Resolvendo o problema:

Como você só pode ter apenas duas coleções com a FetchType.Eager(coleção buscada ansiosamente) em uma classe Entity e porque o carregamento lento é mais eficiente do que o carregamento ansioso, acho que essa maneira de resolver seu problema é melhor do que apenas mudar FetchTypepara ansioso:

Se você deseja que a coleção preguiçosa seja inicializada e também faça com que isso funcione, é melhor adicionar este trecho de código ao seu 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>

O que esse código faz é que aumentará o tamanho do seu JPA sessionou, conforme a documentação diz, é usado "to allow for lazy loading in web views despite the original transactions already being completed."para que a sessão JPA seja aberta um pouco mais e, por isso, você pode carregar preguiçosamente coleções nos arquivos jsp e nas classes do controlador .


7
Por que a sessão JPS está fechada? Como fazê-lo não ser fechado? Como executar uma coleção lenta?
Dims

11
O que define um limite de duas coleções FetchType.Eager por entidade?
Chrisinmtown 16/11/19

No Spring Boot, você pode adicionar 'spring.jpa.open-in-view = true' a 'application.properties'
Askar

28

O motivo é que, quando você usa carga lenta, a sessão é fechada.

Existem duas soluções.

  1. Não use carga lenta.

    Defina lazy=falsena @OneToMany(fetch = FetchType.EAGER)anotação XML ou Set In.

  2. Use carga lenta.

    Defina lazy=truena @OneToMany(fetch = FetchType.LAZY)anotação XML ou Set In.

    e adicione OpenSessionInViewFilter filterseuweb.xml

Detalhe Veja meu POST .


11
... e, no entanto, ambas as soluções não são boas. Sugerir usando o EAGER pode criar enormes problemas. O uso do OpenSessionInViewFilter é um anti-padrão.
1155 Rafael Rafael

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

eu resolvo esse problema adicionando @Transactional, acho que isso pode abrir a sessão


Por que isso recebeu um voto negativo? Adicionando uma transação para a operação se estende a sessão
Tudor Grigoriu

11
É uma prática ruim ad @Transactional para o controlador.
1155 Rafael Rafael

@ Rafael Por que é uma prática ruim?
Amr Ellafy

@AmrEllafy -> Aqui está uma boa explicação: stackoverflow.com/a/18498834/1261162
Rafael

22

O problema é causado pelo acesso a um atributo com a sessão de hibernação fechada. Você não possui uma transação de hibernação no controlador.

Soluções possíveis:

  1. Faça toda essa lógica, na camada de serviço (com o @Transactional), não no controlador. Deve haver o lugar certo para fazer isso, isso faz parte da lógica do aplicativo, não no controlador (neste caso, uma interface para carregar o modelo). Todas as operações na camada de serviço devem ser transacionais. Por exemplo: Mova esta linha para o método TopicService.findTopicByID:

    Coleção commentList = topicById.getComments ();

  2. Use 'ansioso' em vez de 'preguiçoso' . Agora você não está usando 'preguiçoso'. Não é uma solução real, se você deseja usá-lo, funciona como uma solução temporária (muito temporária).

  3. use @Transactional no controlador . Não deve ser usado aqui, você está misturando a camada de serviço com a apresentação, não é um bom design.
  4. use OpenSessionInViewFilter , muitas desvantagens relatadas, possível instabilidade.

Em geral, a melhor solução é a 1.


2
Fetch tipo de assumiu Eager que hibernação será puxado todos os dados na primeira consulta, nem todos os lugares é corretamente
Жасулан Бердибеков

Você deve verificar se a MELHOR SOLUÇÃO É 1 ... de fato, é a ÚNICA BOA solução, pois todas as outras são anti-padrões!
Rafael

19

Para carregar lentamente uma coleção, deve haver uma sessão ativa. Em um aplicativo Web, há duas maneiras de fazer isso. Você pode usar o padrão Open Session In View , em que você usa um interceptador para abrir a sessão no início da solicitação e fechá-la no final. O risco é que você precise ter um tratamento sólido de exceções ou poderá vincular todas as suas sessões e o aplicativo poderá travar.

A outra maneira de lidar com isso é coletar todos os dados necessários em seu controlador, fechar sua sessão e inserir os dados em seu modelo. Pessoalmente, prefiro essa abordagem, pois parece um pouco mais próxima do espírito do padrão MVC. Além disso, se você receber um erro do banco de dados dessa maneira, poderá lidar com isso muito melhor do que se ocorrer no seu renderizador de exibição. Seu amigo nesse cenário é Hibernate.initialize (myTopic.getComments ()). Você também precisará anexar novamente o objeto à sessão, pois está criando uma nova transação com cada solicitação. Use session.lock (myTopic, LockMode.NONE) para isso.


15

Como expliquei neste artigo , a melhor maneira de lidar LazyInitializationExceptioncom isso é buscá-lo no momento da consulta, assim:

select t
from Topic t
left join fetch t.comments

Você deve sempre evitar os seguintes antipadrões:

Portanto, verifique se suas FetchType.LAZYassociações foram inicializadas no momento da consulta ou dentro do @Transactionalescopo original, usando Hibernate.initializepara coleções secundárias.


11
Vlad, você tem alguma sugestão para trabalhar com uma coleção inicializada preguiçosamente em uma entidade buscada pelo método findById () de um repositório gerado pelo Spring? Não estou escrevendo a consulta e a transação está fora do meu código.
Chrisinmtown 12/12/19

Confira este artigo para obter mais detalhes sobre como inicializar coleções lentas.
Vlad Mihalcea

Você poderia esclarecer o que você quer dizer com 'dentro do escopo @Transactional original'? Isso não está claro para mim, pois parece que eu recebo esse erro em uma sessão aberta (mas não a correta?)
Michiel Haisma

Enquanto estiver dentro do escopo, o método de serviço transacional mais importante, também conhecido como gateway de transação. Verifique o TrassctionInterceptorrastreamento na pilha e é esse.
Vlad Mihalcea 21/03/19

Uma das melhores respostas de longe ... isso deve ser marcado como correto. BTW ... supondo que OSIV seja um antipadrão, como é possível ativar por padrão nas versões recentes de inicialização por mola? ... talvez não seja tão ruim assim?
1155 Rafael Rafael

10

Se você está tentando ter uma relação entre uma entidade e uma coleção ou uma lista de objetos java (por exemplo, tipo Long), seria algo como isto:

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

11
em muitos casos, você realmente não quer fazer isso. Você perde todos os benefícios de carregamento lento aqui
kiedysktos

Usar o EAGER não é uma solução profissional.
Rafael

9

Uma das melhores soluções é incluir o seguinte no arquivo application.properties: spring.jpa.properties.hibernate.enable_lazy_load_no_trans = true


11
Você pode dizer ao OP o que ele faz exatamente: efeitos colaterais, impacto no desempenho?
PES

3
Na parte posterior do carregamento lento, uma nova sessão é bifurcada toda vez que uma associação é carregada lentamente, portanto, mais conexões são bifurcadas e cria um pouco de pressão no pool de conexões. Se você tiver um limite no número de conexões, talvez essa propriedade não seja a correta.
precisa saber é o seguinte

2
para alguns, é considerado um anti-padrão vladmihalcea.com/…
Uri Loya

7

Eu descobri que declarar @PersistenceContextcomo EXTENDEDtambém resolve esse problema:

@PersistenceContext(type = PersistenceContextType.EXTENDED)

11
Oi, tenha cuidado com essas alterações. A criação do contexto de persistência no escopo TRANSACTION é preguiçosa, que era a intenção do OP. Portanto, a questão é se você quer ser apátrida ou não. Essa configuração depende da finalidade do sistema e não deve ser alterada muito ... ansiosamente. Se você souber o que quero dizer. Leia aqui stackoverflow.com/questions/2547817/…
kiedysktos

Perigoso. Esta não é a resposta correta. Existem outros acima, muito mais precisos e seguros.
Rafael

5

foi o problema que enfrentei recentemente e resolvi usando

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

descrição mais detalhada aqui e isso salvou meu dia.


5

sua lista tem carregamento lento, por isso não foi carregada. ligar para entrar na lista não é suficiente. use no Hibernate.initialize para iniciar a lista. Se não funcionar, execute o elemento list e chame Hibernate.initialize para cada um. isso precisa ser feito antes que você retorne do escopo da transação. olhe para este post.
procurar por -

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 o problema no meu caso, estava faltando esta linha

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

no arquivo de contexto do aplicativo.

A @Transactionalanotação sobre um método não foi levada em consideração.

Espero que a resposta ajude alguém


4

A anotação @Transactional no controlador está ausente

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

17
Eu argumentaria que o gerenciamento de transações pertence à camada de serviço em que reside a lógica de negócios.
Sõber

A anotação transacional não está faltando. O controlador não deve ter essa anotação. Essas anotações devem estar no nível de serviço.
Rafael

4

Usando a @Transactionalanotação de hibernação , se você obtiver um objeto do banco de dados com atributos buscados preguiçosamente, poderá obtê-los simplesmente buscando esses atributos da seguinte maneira:

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

Aqui, em uma transação gerenciada por proxy do Hibernate, o fato de chamar ticket.getSales()faz outra consulta para buscar vendas porque você a pediu explicitamente.


4

Duas coisas que você deve ter fetch = FetchType.LAZY.

@Transactional

e

Hibernate.initialize(topicById.getComments());

2

Para aqueles que trabalham com Critérios , descobri que

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

fiz tudo o que eu precisava tinha feito.

O modo de busca inicial para coleções é definido como FetchMode.LAZY para fornecer desempenho, mas quando preciso dos dados, apenas adiciono essa linha e aprecio os objetos totalmente preenchidos.


2

No meu caso, o seguinte código foi um problema:

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

Porque ele foi desconectado do banco de dados e o Hibernate não recuperou mais a lista do campo quando necessário. Então, eu o inicializo antes de desanexar:

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

1

O motivo é que você está tentando obter a commentList no seu controlador depois de fechar a sessão dentro do serviço.

topicById.getComments();

Acima, o commentList será carregado apenas se a sua sessão de hibernação estiver ativa, o que eu acho que você encerrou no seu serviço.

Portanto, você precisa obter a commentList antes de fechar a sessão.


2
Sim, esta é a declaração do problema, você também deve fornecer uma resposta em umaAnswer
Sarz

1

A coleção commentsna sua classe de modelo Topicé carregada lentamente, que é o comportamento padrão se você não anotá-la comfetch = FetchType.EAGER especificamente.

É muito provável que o seu findTopicByIDserviço esteja usando uma sessão do Hibernate sem estado. Uma sessão sem estado não possui o cache de primeiro nível, ou seja, nenhum contexto de persistência. Mais tarde, quando você tentar iterar comments, o Hibernate lançará uma exceção.

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

A solução pode ser:

  1. Anotar commentscomfetch = FetchType.EAGER

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "topic", cascade = CascadeType.ALL)   
    private Collection<Comment> comments = new LinkedHashSet<Comment>();
  2. Se você ainda deseja que os comentários sejam carregados com preguiça, use as sessões com estado do Hibernate , para que você possa buscar comentários posteriormente, sob demanda.


1

No meu caso, eu tinha o mapeamento b / w Ae Bcomo

A tem

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

na DAOcamada, o método precisa ser anotado @Transactionalse você não anotou o mapeamento com Tipo de busca - Ansioso


1

Não é a melhor solução, mas para aqueles que enfrentam LazyInitializationExceptionespecialmente Serializationisso ajudará. Aqui você verificará as propriedades inicializadas lentamente e as configurações nullpara elas. Para isso crie a classe abaixo

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

Dentro da classe Entity, na qual você está tendo propriedades inicializadas preguiçosamente, adicione um método como mostrado abaixo. Adicione todas as suas propriedades de carregamento lento dentro deste método.

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

Chame esse checkLazyIntialzation()método depois em todos os locais em que você está carregando dados.

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

0

Olá, Tudo postando muito tarde espero que ajude outras pessoas, Agradecendo antecipadamente a @GMK por este post Hibernate.initialize (object)

quando preguiçoso = "verdadeiro"

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

Agora, se eu acessar 'set' após o encerramento da sessão, ele lança uma exceção.

Minha solução:

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

agora posso acessar 'set' mesmo depois de fechar a sessão de hibernação.


0

Ainda outra maneira de fazer isso, você pode usar o TransactionTemplate para contornar a busca lenta. Gostar

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

0

O problema é causado porque o código está acessando uma relação JPA lenta quando a "conexão" com o banco de dados é fechada ( contexto de persistência é o nome correto em termos de Hibernate / JPA).

Uma maneira simples de resolvê-lo no Spring Boot é definir uma camada de serviço e usar o @Transactional anotação. Essa anotação em um método cria uma transação que se propaga na camada do repositório e mantém aberto o contexto de persistência até o término do método. Se você acessar a coleção dentro do método transacional, o Hibernate / JPA buscará os dados do banco de dados.

No seu caso, você só precisa anotar @Transactionalo método findTopicByID(id)no seu TopicServicee forçar a busca da coleção nesse método (por exemplo, perguntando o seu tamanho):

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

0

Para se livrar da exceção de inicialização lenta, você não deve solicitar uma coleção lenta quando operar com um objeto desanexado.

Na minha opinião, a melhor abordagem é usar o DTO, e não a entidade. Nesse caso, você pode definir explicitamente os campos que deseja usar. Como sempre, é o suficiente. Não é necessário se preocupar que algo como jackson ObjectMapperou hashCodegerado pelo Lombok chame seus métodos implicitamente.

Em alguns casos específicos, você pode usar a @EntityGrpaphanotação, que permite eagercarregar, mesmo se houver fetchType=lazyem sua entidade.


0

Existem várias soluções para esse problema de inicialização lenta -

1) Altere o tipo de busca de associação de LAZY para EAGER, mas isso não é uma boa prática, pois isso prejudicará o desempenho.

2) Use FetchType.LAZY no Objeto associado e também use a anotação Transacional no método da camada de serviço para que a sessão permaneça aberta e quando você chamar topicById.getComments (), o objeto filho (comentários) será carregado.

3) Além disso, tente usar o objeto DTO em vez da entidade na camada do controlador. No seu caso, a sessão é fechada na camada do controlador. É melhor converter a entidade em DTO na camada de serviço.


-11

eu resolvi usando a lista em vez de definir:

private List<Categories> children = new ArrayList<Categories>();
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.