quarta-feira, 9 de março de 2016

JPA - Propriedade Lazy Loading, erro ao carregar coleções.

Nesse post apresento três formas de resolver erros de Lazy Loading.
O JPA desenvolveu uma funcionalidade chamada de Lazy Loading para os atributos de uma classe. O Lazy Loading nada mais é do que, a informação desejada só será trazida do banco de dados quando ela for usada.
É exatamente no momento da solicitação de acesso a uma coleção lazy que o erro acontece. O JPA/Hibernate tenta acessar o banco de dados para buscar a informação que foi definida como Lazy, mas a conexão foi fechada. Por isso que a exceção acontece, pela falta de uma conexão aberta.
Todo relacionamento que termina em Many é lazy por default:@OneToMany, @ManyToMany. Todo relacionamento que termine em One é EAGER por default: @OneToOne. 
Para definir um campo simples (ex.: String nome) como Lazy basta fazer @Basic(fetch=FetchType.LAZY), dessa forma o campo mesmo sendo simples terá o seu carregamento apenas quando for usado. Todos os campos simples (ex.: String, int, double) que se encontram com dentro da classe sem a configuração LAZY são considerados como EAGER.
         Atualmente o modo mais simples de trazer a lista na primeira vez que seu objeto for carregado é alterando a anotação.


@OneToMany(fetch=FetchType.EAGER)

Esse tipo de abordagem é aconselhável apenas para casos em que temos a certeza de que a lista será sempre pequena.

A segunda maneira é por Open Session in View (ou Transaction View para outros). É um padrão de projeto onde você deixa a conexão aberta durante toda a requisição do usuário. Quando o acesso à lista for feito pela página o Hibernate irá realizar uma consulta no banco de dados sem problemas, nenhuma exceção será lançada.

Esse padrão de projeto para web utiliza uma classe que implementa a interface Filter; filtro irá receber todas as requisições feitas ao servidor e realizar duas ações básicas: abrir a transação antes de encaminhar a requisição e finalizar a transação na volta da requisição. Assim, é garantido que o hibernate irá conseguir realizar as transações. Não se esqueça de configurar o filtro no arquivo web.xml.

package com.filter;
import java.io.IOException;
import javax.annotation.Resource;
import javax.servlet.*;
import javax.transaction.UserTransaction;
public class ConnectionFilter implements Filter {
    @Override
    public void destroy() {
    }
    @Resource
    private UserTransaction utx;
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
        try {
            utx.begin();
            chain.doFilter(request, response);
            utx.commit();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    @Override
    public void init(FilterConfig arg0) throws ServletException {
    }
}

É necessário ter bastante cuidado com erros nas transações. Uma mensagem de sucesso pode ser enviada pelo ManagedBean/Servlet e um erro acontecer no filtro. Pode gerar o chamado n+1 de consultas. O objeto A tem uma lista de objetos do tipo B, que para ser carregada, será realizada uma nova consulta no banco. Se o objeto B tiver uma lista de objetos do tipo C, esta por sua vez irá gerar uma nova consulta, e assim por diante. Esse é uma coisa a se considerar nesta“abordagem”. Uma consulta pode gerar um número grande de outras consultas.

A terceira maneira que vou abordar é através de uma Join Query no momento da consulta. Cria-se no DAO uma consulta alternativa, que além de buscar o objeto principal, faz-se um join trazendo os objetos da lista que compõe o objeto principal.

public Pessoa buscaPessoaComAnimais(Long codigo) {

    Query query = entityManager.createQuery("select p from Pessoa p join fetch p.animais where p.codigo = :codigo");
    query.setParameter("codigo", codigo);
    Pessoa pessoa = (Person) query.getSingleResult();
    return pessoa;

}


Assim quando necessitar de trazer a pessoa e a lista de animais vinculados a ela, usa-se esse método criado especificamente para isso. A única desvantagem dessa abordagem se dá pelo fato de ser necessário uma consulta para cada lista de objetos que se deseja usar.

         Ainda existe outra abordagem, que se aplica para EJB's. Caso queira verificar a outra maneira, pode dar uma olhada no link citado na fonte.

         Uma dica interessante, é que alguns componentes do primefaces tem uma propriedade chamada "collectionType", como é o caso do <selectManyCheckbox>, e em alguns casos, mesmo com a configuração do sistema para trabalhar com o Lay Loading já realizada, esses componentes podem continuar apresentando esse erro. Para resolver, informe esta propriedade no componente passando qual coleção você irá usar, como no exemplo:

collectionType="java.util.ArrayList"
          Fonte: http://uaihebert.com/quatro-solucoes-para-lazyinitializationexception

Nenhum comentário:

Postar um comentário