Enfrentando a LazyInitializationException no Hibernate

Postado em 13. out, 2009 por em Java

Sem dúvida o primeiro balde de água fria que levamos ao começar a trabalhar com o Hibernate é a LazyInitializationException. Afinal, quando e por que ela acontece?

Para chegar lá precisamos de um exemplo de relacionamento: uma nota fiscal tem vários itens de compra, um produto tem uma categoria:

@Entity
class NotaFiscal {
  …
  @OneToMany
  List<Item> items;
}

Depois de mapeadas nossas entidades, podemos facilmente percorrer esse relacionamento através do acesso a um getter:

NotaFiscal nf = (NotaFiscal) session.load(NotaFiscal.class, 42);
List<Item> items = nf.getItems();

Nesse caso o Hibernate fará dois selects: um para pegar os dados da NotaFiscal com id 42, e outro procurando todos os items where nota_fiscal_id=42. Há também a possibilidade de indicar para o Hibernate de que tudo deva ser feito em um único join, para isso basta configurar que esse relacionamento deve ser pego (fetched) de maneira ansiosa, prontamente (eager):

class NotaFiscal {
  ...
  @OneToMany(fetch=FetchType.EAGER)
  List<Item> items;
}

Vale relembrar os defaults do Hibernate: todos os relacionamentos *ToOne são EAGER, e os *ToMany são LAZY, isso porque relações *toMany são provavelmente mais custosas, trazendo mais objetos para a memória.

É muito interessante ter relacionamentos LAZY: os dados são puxados apenas quando realmente necessários. Ao mesmo tempo deve-se tomar cuidado: pode gerar o problema das n+1 queries, e muitas vezes sabemos que tal relacionamento será tão utilizado, que deve ser feito de maneira EAGER, evitando mais uma query ser disparada ao banco de dados.

Como exatamente funciona o relacionamento lazy? O Hibernate tira proveito de proxies dinâmicas: ele te devolve objetos que fingem ser listas nesse caso, e quando você invoca algum método deles, o Hibernate então faz a respectiva query para carregar o relacionamento. E há um momento em que isso falha: quando a sessão que carregou o objeto já estiver fechada, como no seguinte código caso o relacionamento seja LAZY:

Session session = sessionFactory.openSession();
NotaFiscal nf = (NotaFiscal) session.load(NotaFiscal.class, 42);
session.close();

List<Item> items = nf.getItems();
System.out.println("numero de pedidos dessa nota:" + items.size());

Esse código tem o seguinte resultado:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection - no session or session was closed.

Ao invocar o método size() a proxy dinâmica devolvida pelo getItems() tenta se conectar ao banco para puxar todos os itens dessa nota, porém a sessão já foi fechada! Por que o Hibernate não reabre a sessão? Como eles mesmos afirmam, não faria mais sentido ter demarcação de transação e nem de abertura e fechamento de sessões! E essa responsabilidade seria demais para um framework ORM, pois não é tão óbvio decidir sobre o ciclo de vida de objetos caros, como a Session e a Connection, que poderiam acabar sendo abertas inúmeras vezes para a renderização de uma única página! Outros frameworks de mapeamento objeto relacional usam essa outra abordagem e reconectam a sessão e conexão caso necessário, que eu também considero uma má escolha.

Para resolver este caso acima parece simples: sempre fechar a sessão ao término do trabalho. Mas e quando estamos trabalhando na Web? Após pegarmos os dados necessários no nosso controlador, fechamos a sessão e passamos os objetos ao JSP através de atributos. O JSP, ao acessar um getter do seu objeto para fazer um loop, como ${notaFiscal.items}, recebe LazyInitializationException da mesma forma. Em um primeiro momento o desenvolvedor muda o relacionamento para EAGER, mas isso gera uma enorme sobrecarga, pois provavelmente em muitos lugares não era necessário carregar todos os itens da compra sempre que uma determinada nota fiscal é requisitada.

Como então evitar a LazyInitializationException sem modificar o relacionamento para EAGER?

Open Session In View

A solução é manter a session aberta durante a renderização da camada de visualização, ou como o Hibernate chama esse pequeno padrão: open session in view. A idéia é bastante simples: a sessão deve ser mantida aberta até o fim da renderização do JSP (ou de qualquer outra camada de apresentação). Isso pode ser obtido através da implementação de um Servlet Filter, algum tipo de interceptador do seu framework preferido ou até mesmo aspectos.

O Spring foi sem dúvida um dos primeiros frameworks a já trazer classes para isso embutidas (assim como também foi o primeiro a fazer o wrap da HibernateException dentro de uma exceção unchecked, pois até o Hibernate 2.x essa exceção era checked). Há as classes OpenSessionInViewInterceptor e sua análoga OpenEntityManagerInViewInterceptor. No VRaptor, além de você pode usar os componentes embutidos do Spring, já existem componentes que fazem o mesmo trabalho (ver componentes embutidos).

Aproveite para ler aqui no blog da Caelum a respeito de outras exceptions frequentes no Hibernate, sobre os estados de uma entidade na JPA, hábitos importantes para todo desenvolvedor Hibernate e mapeamento de herança, além de muitos outros artigos relacionados ao framework. O nosso curso FJ-25 trata bastante de Hibernate e JPA2 com detalhes importantes do dia a dia como esses.

Tags: , , , , , , ,

22 Respostas para “Enfrentando a LazyInitializationException no Hibernate”

  1. Lennon Manchester

    13. out, 2009

    Muito bom o post, parabens Paulo.

  2. Rafael Ponte

    13. out, 2009

    Muito bem explanado o problema e sua solução.

    Este problema é bastante comum no desenvolvimento web, principalmente para quem está iniciando com o framework (Hibernate).

    Vale salientar que o Open Session In View funciona apenas dentro da requisição atual. Na próxima requisição, caso tente se obter um objeto colocado, por exemplo, na Session (do Container), poderá ocorrer ainda um LazyInitializationException ao tentar pegar um relacionamento Lazy.

    Enfim, parabéns pelo post.
    Um abraço.

  3. ovelhosafado

    13. out, 2009

    Muito bom!

    Mas como fariamos isso com ejb3, quando a sessão é criada no servidor?

  4. Andre Brito

    13. out, 2009

    Great job :)
    Me foi apresentada várias vezes essa Exception e quase cheguei a implementar o interceptor pra Flex e Java. Sem chance… (sem tempo).
    Paulo, você já falou sobre algumas Exceptions que ocorre quando usamos JPA. Me ajudou bastante já, pois antes me deparava muito com as ‘coleguinhas’ da LazyInitializationException: a TOE e a POE. Quem nunca que viu uma Exception dessas que atire a primeira pedra.
    Eu vi que você falou do Interceptor. Eu ouvi dizer que tem outro jeito (que você já deve saber também e deve ter falado mas eu devo ter pulado): via filter no web.xml. Nunca tentei… Com Flex, tenho que usar EAGER na Annotation porque não é simples como fazer um Interceptor ‘para’ Servlet (se eu usar Lazy, ele chega no Flex como Eager e ocorre a Exception). De qualquer forma, seria interessante achar uns caras que entendem de Flex e BlazeDS pra fazer uma implementação porque um jeito deve ter!

    Abraço e ótimo post! Parabéns!

  5. Paulo Silveira

    13. out, 2009

    @Danilo

    Excelente pergunta.

    Aí o proprio servidor gerencia isso, porem, quando a requisição terminar e a entidade for serializada ao cliente, a entidade esta detached! Qualquer chamada a uma dynamic proxy ai vai gerar a LazyInitializationException! Voce mesmo precisa controlar isso, infelizmente, ou ainda trabalhar com DTOs em casos mais graves (é, as raros casos eles reaparecem em ejb3)

    Ao no fim desse artigo tem mais sobre esse caso especifico:
    http://blog.caelum.com.br/2006/11/01/transientobjectexception-lazyinitializationexception-e-outras-famosas-do-hibernate/

    abracos

  6. Paulo Silveira

    13. out, 2009

    @Andre Brito

    Pois é. Não sou conhecedor de BlazeDS, mas sei que ele tem uma maneira elegante de você demarcar quando ele deve inicializar a proxy dinâmica (e ele mesmo fará o Hibernate.initialize()).

  7. Wilson

    13. out, 2009

    Muito bacana o post, esse é um problema muito comum quando estamos começando os estudos com o Hibernate. As dicas sobre como contornar esse problema de maneira correta também é muito útil, pois a maioria dos programadores colocariam EAGER ao receber essa Exception, até mesmo por não saber qual o caminho correto, ou por preguiça mesmo.

    Só uma observação, nas duas vezes que vc declarou a variável nf, vc a trocou por notaFiscal.getItems(); logo embaixo dela. Onde o correto seria nf.getItems();

    Forte abraço.

  8. Paulo Silveira

    13. out, 2009

    @Wilson muito obrigado pelo comentário e pela correção :) .

  9. Marcelo

    14. out, 2009

    Muito bom, será de grande valia a quem está começando e ainda não entendeu por que teve essa Exception de forma bem clara.
    Enfim, ainda estou tendo dificuldades pra implementar o OpenSessionInView.
    No caso tenho um sistema Web com JSF+facelets+richfaces (sem seam, sem spring e sem chances de adicionar algo do tipo ao projeto).
    Pelo que entendi quando a fase de renderização acabar a sessão será fechada da mesma forma, então se eu abrir outra requisição ajax (a4j) terei o LazyInitializationException de novo, confere?
    Ainda está muito dificil de encontrar materiais que me auxilie em como implementar o OpenSessionInView no cenário que descrevi, alguém teria alguma sugestão?

    Abraços.

  10. Paulo Silveira

    14. out, 2009

    Oi Marcelo!

    Bem, se você não tem uma alternativa como Spring e Seam, você pode fazer uma Servlet Filter, mesmo usando JSF, que coloca a session dentro de um request attribute. Depois você injeta isso no managed bean através do managed-property no faces-config.xml.

    Post bom sobre habitos de JSF, de uma olhada no blog do Rafael Ponte:
    http://www.rponte.com.br/2009/01/19/o-que-todo-bom-desenvolvedor-jsf-deveria-saber/

    Tambem veja a palestra dele sobre os 10 maus habitos do programador JSF:
    http://www.rponte.com.br/2008/11/24/os-10-maus-habitos-dos-desenvolvedores-jsf/

  11. Oracle

    22. out, 2009

    Só uma dica: No caso da lista lazy é possivel um meio termo entre Lazy e Eager usando o @BatchSize(size=20). Com isso isso ao inves de gerar uma query por entrada na list(ao iterar sobre ela) ele faz queries em batch. Colocar um batch é uma boa pratica ao usar listas lazy.

    Como o Wilson falou muitos usam o Eager de forma errada.

  12. Paulo Silveira

    22. out, 2009

    Ola @Oracle (?), verdade sobre o batch size. Essa importante dica e outras estão aqui:
    http://blog.caelum.com.br/2008/01/28/os-7-habitos-dos-desenvolvedores-hibernate-e-jpa-altamente-eficazes/

  13. Marcos Cunha

    19. jan, 2010

    Paulo, gostei do post pois me identifiquei com o problema no final do texto. Porém, uso Struts 2, e agora? Existe um “OpenSessionInView” para o struts? Grande Abraço

  14. Paulo Silveira

    26. mar, 2010

    Oi Marcos! Voce pode implementar isso atraves dos interceptadores do struts 2! Ou mesmo atraves de uma servlet filter

  15. Fernando

    06. out, 2010

    Tive este problema quando usava JPA com Hibernate. Mas não mandei explicitamente em lugar algum que a session fosse fechada. Como, usando JPA, posso manter a minha session aberta?

  16. Paulo Silveira

    08. out, 2010

    Ola Fernando. A solucao é a mesma: fazer um ” Open Entity Manager in View”. Basta nao fechar o EntityManager, só fazendo isso ao término do processamento do template/JSP.

  17. Vitor Albuquerque

    11. jan, 2011

    Olá Paulo, muito interessante o post, parabéns. Atualmente estou enfrentando um problema de LIE na view e postei no guj em busca de ajuda. http://www.guj.com.br/java/229642-lazy-fetching-na-view—lazyinitializationexception—hibernate-36–spring-30#1177987 . Se pudesse dar uma luz ao problema agradeceria bastante. Obrigado.

  18. Vidal

    24. mai, 2011

    Paulo muito bom o post. Procurei muito sobre a solução desse problema e a unica forma que ache na internet, era dessa forma que vc disse.

    Só que esta forma me cheira um tanto quanto POG, pois vc está colocando dentro de um filtro uma coisa que trata de acesso ao banco de dados.

    Ao meu ver esta quebrando todo padrão mvc!

  19. Paulo Silveira

    24. mai, 2011

    Ola Vidal

    Entendo seu ponto, mas não ha correlacao com quebra de MVC em usar um filtro para tratar do banco de dados. Se fosse assim, todos interceptadores e aspectos de um servidor de aplicacao estariam quebrados.

    MVC nao quer dizer que tudo que é do banco deve ficar em um unico lugar, e sim que ele nao deve se misturar com muito acoplamento com as outras partes. Se esse seu filtro serve apenas para isso, ele está muito bem isolado e não atravessa layers diferentes.

    Essa é a forma que vao recomendar usar o hibernate em diversos frameworks mvc, do jsf ao struts e vraptor;

  20. Thiago Marques

    09. set, 2011

    Estou com esse problema no J2SE, qual seria uma solução viável?
    Na web soluciono implementando filtro/spring, só que no desktop não encontrei a solução para o mesmo problema.

    Alguém poderia me indicar um caminho?

Trackbacks/Pingbacks

  1. Enfrentando a LazyInitializationException no Hibernate « Blog – Fábio Zoroastro - outubro 13, 2009

    [...] sobre isso que o Paulo Silveira falou neste post no blog da [...]

  2. Camilo Lopes – LpJava » Open Session View – Hibernate Solução - julho 13, 2010

    [...] Vou usar a técnica de reutilização da informação então o Paulo da Caelum já fez uma abordagem excelente do porque desse problema , veja. [...]

Deixar uma Resposta