Enfrentando a LazyInitializationException no Hibernate

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.

32 Comentários

  1. Lennon Manchester 13/10/2009 at 14:26 #

    Muito bom o post, parabens Paulo.

  2. Rafael Ponte 13/10/2009 at 14:27 #

    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/10/2009 at 21:29 #

    Muito bom!

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

  4. Andre Brito 13/10/2009 at 22:18 #

    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/10/2009 at 22:34 #

    @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/10/2009 at 22:36 #

    @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/10/2009 at 23:10 #

    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/10/2009 at 23:24 #

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

  9. Marcelo 14/10/2009 at 08:55 #

    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/10/2009 at 18:58 #

    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/10/2009 at 09:36 #

    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/10/2009 at 11:49 #

    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/01/2010 at 13:18 #

    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/03/2010 at 22:26 #

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

  15. Fernando 06/10/2010 at 12:22 #

    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/10/2010 at 12:17 #

    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/01/2011 at 04:10 #

    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/05/2011 at 11:22 #

    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/05/2011 at 11:35 #

    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/09/2011 at 14:28 #

    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?

  21. Vinicio 11/03/2012 at 11:20 #

    Mesmo usando o filter do hibernate estou tendo esse problema, não estou conseguindo uma solução.

  22. Virmerson 12/09/2012 at 15:29 #

    Simples e Objetivo! Parabéns pelo post.

  23. Mauricio Magnani Jr 06/05/2013 at 13:32 #

    Esse é classico 🙂

    Bela dica!

    Abs

  24. Ijimero 02/08/2013 at 11:34 #

    Paulo, no trexo aonde você explica como funciona a busca dos objetos relacionados vc diz:

    “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. ”

    existe alguma forma que eu possa modificar a paremetrização “where nota_fiscal_id=42” para alguma outra coisa que eu desejar?

  25. THIAGO 18/11/2014 at 14:28 #

    Cara demais seu post. Obrigado

Deixe uma resposta