Um dos problemas do programador é o LazyInitilizationExecption, isso ocorre quando um objeto precisa de uma conexão e ela não está mais disponível, provocando um erro de inicialização.
Note que não estou utilizando nenhum tipo de Controle de Transação por frameworks, ou seja, o controle de transação é todo feito dentro do próprio método.
Digamos que nossa aplicação possui um entidade chamada Cliente e outra entidade chamada Telefone, onde um cliente pode possuir vários telefones.
vamos as entidades:
@Entity
@Table(name = "cliente")
public class Cliente implements Serializable{
private static final long serialVersionUID = -3494651140532522766L;
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String nomeCliente;
@OneToMany(mappedBy = "cliente", targetEntity = Telefones.class)
@Cascade(value = CascadeType.ALL)
private List telefones = new ArrayList();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getNomeCliente() {
return nomeCliente;
}
public void setNomeCliente(String nomeCliente) {
this.nomeCliente = nomeCliente;
}
public List getTelefones() {
return telefones;
}
public void setTelefones(List telefones) {
this.telefones = telefones;
}
}
@Entity
@Table(name="telefone")
public class Telefones implements Serializable{
/**
*
*/
private static final long serialVersionUID = 1L;
@Id @GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String telefone;
@ManyToOne
@JoinColumn(name = "cliente_id")
private Cliente cliente = new Cliente();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getTelefone() {
return telefone;
}
public void setTelefone(String telefone) {
this.telefone = telefone;
}
public Cliente getCliente() {
return cliente;
}
public void setCliente(Cliente cliente) {
this.cliente = cliente;
}
}
==========================================================
Bom, tenho duas entidades que se relacionam, na Entidade Cliente tenho um mapeamento OneToMany que está mapeado por “cliente” na entidade Telefones. Na Entidade Telefones tenho um relacionamento ManyToOne para a entidade Cliente.
Note que foi definido um Cascade na entidade Cliente, ou seja, nossa aplicação irá salvar o Cliente e o telefone. Por padrão todos os relacionamentos são definidos como Lazy (preguiçoso) e isso gera o LIE.
Note também que nossas entidades foram instanciadas, não está sendo feito uma inversão de controle até mesmo pq esse não é o tema do Post.
Bom… como vamos prevenir o LIE nesse caso, existem algumas soluções e fica a critério do programador definir qual a melhor solução para a sua aplicação.
Solução Nº 1.
A primeira solução e mais facil seria colocar o atributo: @LazyCollection(LazyCollectionOption.FALSE)
Isso iria fazer com que o relacionamento não fosse mais Lazy, ou seja, sempre que eu precisar de um cliente ele iria trazer todos os Telefones.
Essa solução implica em um problema sério. Imagine uma aplicação onde existem vários relacionamentos, digamos, uma aplicação de vendas onde um cliente possui uma compra, uma compra possui vários produtos, pra cada produto exista um fornecedor, pra cada fornecedor exista um endereço.
Nesta situação, ao recuperar uma venda seria feito uma query no banco buscando por este cliente, com esta query todos os objetos do banco (cliente, produto, fornecedor, endereço, etc) iriam ser inicializados naquele momento. Isso gera um problema quando a sua aplicação fica grande, quanto mais informações vc tem no BD mais tempo será necessário para poder realizar tal query. Esta solução, com o tempo, iria matar a sua aplicação.
Solução Nº 2:
Alguns programadores gostam de criar um filtro para abrir e fechar a conexão, prevenindo o LIE dessa maneira pois a conexão estaria aberta enquanto o objeto fosse necessário. Está seria uma solução mais “elegante” porém vc estaria preso a este filtro.
Solução Nº 3:
Você poderá inicializar os objetos que deseja manualmente.
Peguemos o exemplo do cliente / telefones, vamos fazer uma query para trazer apenas o cliente com o ID 1, então temos o seguinte:
public Cliente findById(Long id){
Cliente cliente = null;
try {
getHibernate().getSession().beginTransaction();
cliente = (Cliente) getHibernate().getSession().
get(Cliente.class, id);
for(Telefones tel: cliente.getTelefones()){
Hibernate.initialize(tel);
}
getHibernate().getSession().
getTransaction().commit();
} catch (Exception e) {
getHibernate().getSession().getTransaction().rollback();
} finally {
getHibernate().getSession().close();
}
return cliente;
}
Note que o Hibernate.initialize está inicializando cada objeto do tipo Telefones.
Eu particularmente prefiro este método pois consigo controlar a transação.
Basicamente é assim que podemos resolver o problema do LIE, em alguns frameworks mais recentes como o JBoss Seam existe um escopo maior que request e menor que sessão, um escopo de conversação que faz com que o contexto fique ativo até o final de todo o processo. Isso previne o problema do LIE pois o contexto ainda estaria vivo, ou seja, a conexão ainda estaria ativa no momento em que fosse solicitado para exibir os dados da listagem de telefones.
Rafael Ponte
dezembro 30, 2008 at 3:38 am
Solução Nº 4: Utilizar algum framework de escopo conversacional que também estenda/prolongue o contexto de persistência do jpa/hibernate. Como citado por você, temos o JBoss Seam e seus escopos, mas também temos outro muito bom para quem trabalha com Spring, é o Myfaces Orchestra.
Parabéns pelo blog Marcus, vê se continua postando sobre JSF, pois está realmente dificil encontrar blogs nacionais sobre o tema.
Abraços e te cuida.
Marcus Mazzo
dezembro 30, 2008 at 3:47 am
Muito bem lembrado
,
Quanto a continuar postando sobre JSF, to tentando aprender um pouquinho com o Mestre do JSF
, estudando, estudando e estudando. Realmente existem poucos blogs com o tema, to aprendendo com o mestre e logo estarei postando mais coisas sobre JSF.
Vlw