Como funciona o ciclo de vida de classes persistentes no Hibernate
Entenda como evitar exceções do tipo “Lazy Initialization Exception”
Estatísticas:










votos: 4
Serviços:

Como funciona o
ciclo de vida de classes persistentes no Hibernate
Entenda como evitar
exceções do tipo “Lazy Initialization Exception”
Pré-requisitos
É
assumido que o leitor já tenha um conhecimento básico do framework Hibernate,
sabendo criar mapeamentos entre classes e tabelas no banco e que entenda o
mecanismo de lazy loading do Hibernate. Sem esses conhecimentos, o entendimento
desse artigo estará dificultado.
Motivação – Por que as instâncias
têm diferentes estados?
Como
o leitor já sabe, uma classe Java é mapeada no Hibernate como uma tabela no
banco. Cada instância dessa classe corresponde a um registro da tabela alocado
na memória. O mesmo acontece com Entity beans e outras soluções
de mapeamento objeto relacional, que sempre tem estados definidos para essas
instâncias de objetos mapeados.
No
caso do Hibernate, existem 3 estados possíveis para cada instância, os quais
apresento aqui e que serão explicados no decorrer do artigo:
- Transiente
- Persistente
- Desatachado (detached)
Entender
o funcionamento do Hibernate ao lidar com esses objetos é essencial para usar a
ferramenta de maneira adequada e não existirem problemas de manutenibilidade
posteriormente. Vamos entender o que significa cada estado.
Instâncias
transientes
Instâncias
transientes são as mais simples de serem entendidas, pois não há nada de
especial nelas. São instâncias que existem na memória, mas não tem registros
correspondentes existentes na tabela relacionada. Quando você aloca uma
instância de uma classe mapeada com a keyword new do Java, você
simplesmente criou uma instância transiente.
Hibernate usa Proxy
Antes
de apresentar os outros dois estados, convém explicar algo sobre como o
Hibernate funciona por dentro. Vamos supor que tenhamos duas classes: Pessoa e
Telefone, cada uma mapeada para suas respectivas tabelas. Ambas estão
relacionadas no banco e, no Java, Pessoa tem um List
de Telefone, conforme as Listagens 1 e 2 (as anotações dos mapeamentos
não são mostradas):
import java.util.List;
public class Pessoa {
Long id;
String nome;
List<Telefone>
telefones;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public List<Telefone>
getTelefones() {
return telefones;
}
public void setTelefones(List<Telefone>
telefones) {
this.telefones =
telefones;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Listagem 01. Classe Pessoa
public class Telefone {
Long id;
String numero;
public String getNumero() {
return numero;
}
public void setNumero(String numero) {
this.numero = numero;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
}
Listagem 02. Classe Telefone
Quando
criamos uma instância da classe Pessoa com new (Pessoa pessoa = new
Pessoa()) e chamamos o método pessoa.getTelefones(), o que obtemos? Null, é
claro, uma vez que a classe acabou de ser criada.
Considere
agora o código da listagem 3:
public class Consulta {
public void imprimeTelefones() {
Session session;
/* Código para criar uma session
hibernate*/
/* session = SessionFactory. ; ... */
Pessoa pessoa = session.load(Pessoa.class, new Long
(9715));
System.out.println("Nome da
pessoa: " + pessoa.getNome());
int i = 0;
for (Telefone t :
pessoa.getTelefones())
System.out.println("
Telefone " + (i++) + ": "
+ t.getNumero());
}
}
Listagem 03. Consulta de uma
pessoa no banco
Esse
código carrega a pessoa de ID 9715 e imprime seu nome e telefones. Os detalhes
de obtenção da Session foram omitidos no código.
Qual
o resultado da execução desse código? Observe que a instância da classe Pessoa
foi retornada pelo banco, através da chamada ao método load da Session
Hibernate. O resultado, como é de se esperar, depende do número de registros,
na tabela TELEFONE, relacionados com a pessoa de ID 9715.
A
pergunta que agora não quer calar é: o que acontece quando usamos Lazy
Loading? Se o mapeamento entre Pessoa e
Telefone for do tipo lazy, somente quando o método pessoa.getTelefones() for
chamado é que a consulta no banco será feita. Mas o a listagem 1 nos
mostra que esse método não faz consulta nenhuma no banco, apenas retorna o
valor de uma variável pré-existente. Que mágica aconteceu para que os telefones
fossem retornados?
A
resposta é que quando fizemos a consulta da Pessoa no banco usando o método
session.load(), o Hibernate não nos retornou uma instância da classe Pessoa,
como era de se esperar. O Hibernate na verdade retornou um Proxy para a classe
Pessoa, ou seja, uma outra classe, com os mesmos métodos (mesma interface) que
a classe Pessoa, mas que também guarda uma instância da classe real, o target.
Esses
métodos verificam se a consulta no banco já foi feita ou não. Caso não tenha
sido feita, ele faz a consulta e popula o valor do target. Caso contrário, ele
simplesmente retorna o valor da propriedade sendo acessada. Existe um Proxy
criado dinamicamente pelo Hibernate para cada classe mapeada.
Instâncias
persistentes
Agora
ficou fácil entender os outros tipos de instância. Quando fizemos a consulta
por uma pessoa, na listagem 3, o Hibernate nos retornou uma instância
que tem um registro correspondente no banco, incluindo o ID 9715 que usamos
para realizar a busca. Toda vez que o Hibernate nos retorna uma instância
persistente, ele retorna um Proxy para a classe mapeada, ao invés de retornar
um objeto da classe real.
Como
faríamos para realizar uma alteração no registro de buscamos na listagem 3?
Veja a listagem 4:
public class Consulta2 {
public void imprimeTelefones() {
Session session;
/* Código para criar uma session
hibernate*/
/* session = SessionFactory. ; ... */
Pessoa pessoa = session.load(Pessoa.class, new Long
(9715));
System.out.println("Nome da
pessoa: " + pessoa.getNome());
pessoa.setNome("Sr.
" + pessoa.getNome());
session.update (pessoa);
}
}
Listagem 04. Consulta e
alteração de uma pessoa no banco
Chamamos
o método update() da Session para salvar as alterações em nossa instância. Ao
chamarmos esse método, um UPDATE é realizado no banco de dados. Poderíamos usar
o método save() para salvar uma instância transiente e um INSERT seria
realizado no banco de dados.
Instâncias
desatachadas (detached)
Existe
uma coisa no hibernate chamada contexto de persistência. Esse contexto é na
verdade um nome mais bonito para a Session, que representa um caminho de acesso
ao banco. Quando fizemos a consulta pela Pessoa na listagem 4, o Proxy
retornado pelo Hibernate veio associado à Session que foi usada para realizar a
consulta. Se realizássemos a consulta em outra Session, obteríamos uma outra
instância associada à outra Session.
Acontece
que a Session precisa de uma conexão com o banco aberta. Se nossa instância
está em estado persistente, ou seja, existe na tabela e ainda está associada a
uma session aberta, o que acontece quando usamos essa instância para popular
valores em um JSP ou para retornar dados para um outro programa? A Session é
fechada e a instância fica sem um contexto de persistência associado. Quando
isso acontece, o estado do objeto deixa de ser persistente e passa a ser
desatachado.
A
diferença fundamental que isso acarreta é que não podemos mais usar o método
update ou save para tentar salvar nossas alterações no banco, como fizemos na
listagem 4. Para salvar uma instância desatachada, o Hibernate vai usar o ID
para reconhecer o registro no banco e não informações associadas com a Session
guardadas na memória, então usamos o método merge(), conforme ilustra a listagem
5:
public class
Consulta3 {
public void imprimeTelefones() {
Session session;
/* Código
para criar uma session hibernate*/
/* session = SessionFactory.
; ... */
// Pessoa pessoa =
session.load(Pessoa.class, new Long (9715));
Pessoa
pessoa = new Pessoa();
pessoa.setId(new
Long(9715));
pessoa.setNome("Francisco");
pessoa.setTelefones(null);
System.out.println("Nome
da pessoa: " + pessoa.getNome());
pessoa.setNome("Sr.
" + pessoa.getNome());
session.merge
(pessoa);
}
}
Listagem 05. Merge de uma
instância desatachada
Perceba
que ao invés de usarmos o keyword new, poderíamos ter feito uma consulta
no banco, ter passado o objeto para uma JSP e então tentar salvar o objeto
vindo de volta no request.
Lazy Initialization Exception
O
uso de instâncias desatachadas é a principal causa de da exceção “Lazy Initialization Exception”. Ela ocorreria,
por exemplo, se tentássemos chamar o método pessoa.getTelefones() numa
instância desatachada, pois o Hibernate não conseguiria fazer uma consulta no
banco para pegar os telefones sem que a instância esteja associada com uma
sessão aberta. Para solucionar isso, poderiamos chamar o método enquanto a
instância ainda é persistente ou carregar novamente a instância do banco antes
de chamar esse método.
Transient Object Exception
Completando
o artigo, quando ocorre essa famosa exceção? Vejamos a listagem 6:
import
java.util.ArrayList;
import
java.util.List;
public class
TransientExceptionGen {
public void imprimeTelefones() {
Session session;
/* Código
para criar uma session hibernate*/
/* session = SessionFactory.
; ... */
Pessoa pessoa =
session.load(Pessoa.class, new Long (9715));
List<Telefone>
telefones = new ArrayList<Telefone>();
Telefone
telefone = new Telefone();
//veja
que telefone.getId() == null
telefone.setNumero("1234
5678");
pessoa.setTelefones(telefones);
System.out.println("Nome
da pessoa: " + pessoa.getNome());
pessoa.setNome("Sr.
" + pessoa.getNome());
session.update
(pessoa);
}
}
Listagem 06. TransientException
generator
Veja
que Pessoa agrega Telefone e, nesse caso, tentamos salvar uma instância da
classe Pessoa em estado persistente que agrega uma instância de Telefone
transiente. Embora seja possível mudar esse comportamento no mapeamento, a
atitude padrão do Hibernate ao mandá-lo salvar pessoa no banco é tentar salvar
pessoa com todas as suas agregações. Contudo, só é permitido salvar agregações
transientes quando estamos salvando instâncias transientes.
Espero que o artigo tenha ajudado um pouco a entender aquelas situações complicadas que ninguém entende. Nos próximos, pretendo abranger os conceitos básicos. Até lá!







[vídeo] Consumindo os dados via ajax – Consumindo dados com getJson do jQuery utilizando POCO EF 4.0 – Parte 3

[vídeo] Classe POCO – Consumindo dados com getJson do jQuery utilizando POCO EF 4.0 – Parte 1

Imprimindo Relatório : VCL Crystal Reports XI - Delphi

VCL Crystal Reports XI - Delphi

[vídeo] Gerenciamento das Comunicações - Curso Gerência de Projetos – Parte 8

[vídeo] Plano de Comunicação - Curso Gerência de Projetos – Parte 9

[vídeo] Seleção e Priorização de Projetos - Curso Gerência de Projetos – Parte 6

[vídeo] Escopo, metas, premissas, restrições - Curso Gerência de Projetos – Parte 7

[vídeo] Documento Conceitual do Projeto - Curso Gerência de Projetos – Parte 5


Você está em:




Conheça os planos de créditos DevMedia e visualize esse post agora mesmo!


