Arquivos de sites

TransientObjectException, LazyInitializationException e outras famosas do Hibernate

Para quem desenvolve com Hibernate, sem dúvida as exceptions que aparecem mais são a TransientObjectException (TOE), LazyInitializationException (LIE) e a PersistentObjectException (POE). Semana passada tive o prazer de ministrar um treinamento de EJB3 e JSF para o pessoal da Petrobras de 5 cidades diferentes e durante o curso várias TOEs, POEs e LIEs apareceram. Vamos ver quando cada uma ocorre.

Estou usando aqui a API do Java Persistence, mas a relação é direta com o Hibernate, já que este é o meu provider. Considere duas entidades, Autor e Livro, cada uma com atributos triviais, um id que é @GeneratedValue e uma relação @ManyToMany entre elas, sendo Livro que possui o lado mappedBy. Vamos ao código:

Autor a = new Autor();
Livro l = new Livro();
a.setLivros(Collections.singleton(l));
manager.persist(a);

Resultado:

org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: br.com.caelum.hibernate.testes.Livro

Essa exception não ocorreria se você tivesse chamado manager.persist(l); dentro dessa mesma transação, ou utilizasse cascade=CascadeType.PERSIST no relacionamento. Um objeto transiente, ao ser persistido, não pode se referenciar a outros objetos transientes, a não ser que haja cascade!

Agora considere:

Autor a = new Autor();
a.setId(1L);
a.setNome("paulo");
    
manager.persist(a);

Resultado:

org.hibernate.PersistentObjectException: detached entity passed to persist: br.com.caelum.hibernate.testes.Autor

O id de Autor está anotado com @GeneratedValue, quando ele percebe que há um id setado ele imagina que provavelmente esse objeto já deve existir no banco de dados (repare que ele não faz select, se você colocar um id que não existe, a mesma exception ocorrerá). persist não aceita objetos detached, apenas transient e managed. Essa exception não ocorreria se seu id não fosse @GeneratedValue, ou se você tivesse puxado esse Autor através do manager.find, por exemplo.

Vamos passar para o método merge. Porque o utilizamos tanto? Pois na web, quando recebemos os parâmetros e populamos nosso Entity para atualizá-lo, ele está detached: não foi pego através do EntityManager/Session, porém possui um id setado (o que anteriormente gerou a PersistentObjectException). O merge tem um detalhe importantíssimo: ele não vai passar o estado daquela entidade para managed (como faz o saveOrUpdate do Hibernate), e sim devolver uma versão da mesma entidade que seja managed (como fazia o velho saveOrUpdateCopy), em outras palavras, futuras mudanças na entidade passada não surtirão efeito. A outra grande diferença é que agora podemos passar como argumento um objeto transiente que já possua um id setado:

Autor a = new Autor();
a.setId(1L);
a.setNome("livro1"); 
manager.merge(a);
a.setNome("livro2");

O código irá atualizar o nome do Livro de id 1 para livro1. Repare que a mudança para livro2 não surtirá efeito, já que o merge não faz attach do objeto passado como argumento, então nesse caso ele continua detached. Uma opção seria você pegar o retorno do método merge, que é o mesmo Autor, porém agora managed. A documentação do hibernate sobre o merge parece ser melhor que a especificação.

Aqui temos de ter muito cuidado, como não puxamos esse Autor pelo entityManager, caso ele possua alguns livros no seu relacionamento, nesse merge perderíamos todas essas informações!

Parece fácil evitar todas essas exceptions, então porque eu disse que ocorreram tanto no curso? Bem, estávamos usando a JPA de dentro de um container, não standalone. Considere então um session bean de granularidade fina que esteja agindo apenas como um dao (esse não é o ideal, mas fica para os testes):

@Remote
interface SessionBeanRemote {
  void persiste(Autor a);
  void persiste(Livro l);
  Livro buscaLivroPorNome(String nome);
}

E considere o cliente:

Livro l = new Livro();
sessionBeanRemoto.persiste(l);
Autor a = new Autor();
a.setLivros(Collections.singleton(l));
manager.persiste(a);

Repare como esse código é muito parecido com o primeiro desse post, porém já persistindo o Livro anteriormente, para evitar a TransientObjectException. Mas adivinhe, aqui é lançada uma TOE! Isso ocorre porque, apesar do Livro ter sido persistido, no cliente remoto ele estará transiente, pois sua chave primária não foi populada, já que o objeto foi serializado e só no servidor se encontra uma versão desse Livro com sua respectiva chave! Se fosse no EJB 2.x, onde o Entity bean também é um componente, isso não ocorreria. Mas aqui, fora do container, o Entity bean age realmente como um valeu object: não há ligação dele com o servidor.

Para resolver isso temos alguns idiomismos, mudaríamos nosso bean remoto para:

@Remote
interface SessionBeanRemote {
  Autor persiste(Autor a);
  Livro persiste(Livro l);
  Livro buscaLivroPorNome(String nome);
}

Um tanto estranho, e na nossa implementação devolveríamos o próprio argumento:

@Stateless
class SessionBean implements SessionBeanRemote {
  @PersistentContext
  private EntityManager manager;
  public Livro persiste(Livro l) {
    manager.persist(l);
    return l;
  }
  // outros metodos
}

Dessa maneira passaríamos de volta ao cliente uma versão detached do novo livro: agora com ID! Nosso cliente ficaria:

Livro l = new Livro();
l = sessionBeanRemoto.persiste(l); // agora pegamos o retorno!
Autor a = new Autor();
a.setLivros(Collections.singleton(l));
manager.persiste(a);

Existem outras alternativas, como retornar apenas a chave primária, utilizar session beans com maior granularidade, etc.

E a LazyInitializationException que mencionei? Usando hibernate ou JPA standalone estamos cansados de saber como evitá-la: basta manter a sessão aberta durante a renderização na camada de visualização. Mas com EJB a história é outra, sendo muito mais sutil: chame o método buscaPorNome, ele vai retornar ao cliente um Livro detached. Adivinhe o que acontece ao invocar o getAutores() no cliente?

Fonte: Paulo Silveira – Caelum

Quando usar o fetch.LAZY e o fetch.EAGER

Alguns conceitos básicos:

  • EAGER – Tradução ->
  1. Ansioso
  2. Impaciente
  3. Ardente
  class NotaFiscal {
  ...
  @OneToMany(fetch=FetchType.EAGER)
  List items;
  }

Imagine que quando você usa o EAGER e dá um get num objeto, ele traz tudo que está dentro do objeto, ou seja, se há um relacionamento 1 para N, no objeto será carregado todas as referências(N) dele. Se sua aplicação depende de performance, este pode ser um problema, pois isto ocupará um grande espaço de memória carregando todas as listas/dependências do objeto.

  • LAZY – Tradução:
  1. Preguiçoso
  2. Indolente
  3. Lento
  4. Ocioso
  5. Vadio
  @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "tabela_despesas", nullable = false)
    public Despesa getRelatorioDespesa() {
        return this.relatorioDespesa;
    }

Desse modo, quando acessamos o objeto ele não traz instantaneamente todas as dependências. Somente quando precisamos do atributo dependente é que a pesquisa (select ou JOIN) é feito, economizando memória. Para que o LAZY faça este select/JOIN é necessário declarar o fetch, dizer como será a pesquisa, se via Selec ou JOIN e você gera então a consulta.

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. Caelum

Lazy diz QUANDO trazer.lazy=”true” – Traga quando eu precisar ( ou seja. Quando eu acessar ) lazy = “false” – Traga junto com o objeto principal

  • Fetch – Tradução->
  1. buscar
  2. ir buscar
  3. trazer

Fetch diz COMO trazer.fetch = “select” – Faça um select pra pegar esse atributo fetch=”join” – Faça um join com o objeto principal e traga junto com ele. Essa opção ANULA a opção lazy = “true” EAGER, seria equivalente lazy = false.

  1. @Fetch( FetchMode.SELECT )
  2. @Fetch( FetchMode.JOIN )

Todos os relacionamentos *ToOne sãoEAGER, e os *ToMany são LAZY, isso porque relações *toMany são provavelmente mais custosas, trazendo mais objetos para a memória. Caelum

————————————————————————-
Problemas com lazy init (LazyInitializationException)?

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

List 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.

Utilize o open session in view: 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}, recebeLazyInitializationException 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 paraEAGER? 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çãounchecked, pois até o Hibernate 2.x essa exceção era checked). Há as classesOpenSessionInViewInterceptor 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). Leia a matéria completa no blog da CAELUM: http://blog.caelum.com.br/2009/10/13/enfrentando-a-lazyinitializationexception-no-hibernate/  Outro problema:

Hibernate – could not initialize proxy – no Session
  1. 09:15:11,883 ERROR LazyInitializationException:19 – could not initialize proxy – no Session
  2. org.hibernate.LazyInitializationException: could not initialize proxy – no Session
  3.     at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:57)
  4.     at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:111)
  5.     at org.hibernate.proxy.pojo.cglib.CGLIBLazyInitializer.invoke(CGLIBLazyInitializer.java:150)
  6.     at org.iprogramming.model.persistence.dto.Person$$EnhancerByCGLIB$$4dfa7473.getName(<generated>)

Essa exception ocorre porque voce fechou a sessão do hibernate (ou entitymanager do JPA usando hibernate). Provavelmente voces estao fechando a sessao/entityManager antes de renderizar a pagina, isso é, antes de fazer o dispatch!

————————————————————————-
Leia a discussão sobre este problema e outras soluções no GUJ:
http://www.guj.com.br/java/60474-hibernate—could-not-initialize-proxy—no-session
http://www.guj.com.br/java/23319-hibernate-3—-orghibernatelazyinitializationexception-could-not-initialize-proxy—the-owning-se

Fonte: Jardelmorais