Monday, October 16, 2017

Generic Lazy Loading com JSF e PrimeFaces

Lazy Loading é um padrão de projeto que atrasa o carregamento de dados na memória até o momento quando eles são estritamente necessários. Em sistemas orientados a objeto, as entidades estão sempre relacionadas umas com as outras de modo que esses níveis de relacionamento podem se extender indefinidamente dependendo do tamanho do sistema. Se a aplicação não implementa uma estratégia adequada para listar os registros em uma tabela, ela corre o risco de carregar uma quantidade absurda de dados na mémória sem necessidade, o que pode prejudicar seu desempenho.

O componente p:dataTable do framework PrimeFaces permite implementar LazyLoading de forma muito simples bastando que o desenvolvedor estenda LazyDataModel<T>, onde T é o tipo da entidade que será carregada na tabela. Por exemplo, suponha que você tenha uma entidade Pessoa, você deveria extender LazyDataModel da seguinte forma:
public class LazyTablePessoa extends LazyDataModel<Pessoa>{

   private PessoaService service;

   @Override
   public List<Pessoa> load(int first, int pageSize, String sortField, 
                               SortOrder sortOrder, Map<String, Object> filters) {

     
        List<Pessoa>listPessoa = pessoaService.getPessoas(first, first + pageSize);
        int linhas = pessoasService.countPessoas();
        setRowCount(linhas);
        return listPessoa;
    }
  //... outros métodos
}
Essa abordagem possui um problema se o projeto for crescendo e um número cada vez maior de entidades demandar lazy loading em determinadas telas. Dessa forma seria necessário implementar  LazyDataModel para cada entidade que você queira exibir. Se houver 100 entidades para listar, você terá que implementar 100 extensões de LazyDataModel, cada uma com o código praticamente idêntico!

Generic Lazy Data Table
Somente os dados exibidos são carregados na memória.

Podemos reduzir drasticamente essa quantidade de implementações criando uma única extensão genérica de LazyDataModel que atenda, digamos, 90% de todas as necessidades de exibição, ou seja, você podera ter 100 entidades no seu modelo, mas uma única implementação de LazyDataModel será suficiente para listar as entidade de acordo com o padrão lazing.

Para implementar este padrão precisamos criar:
  1. Uma DataTable genérica que extenda LazyDataModel
  2. Uma interface de serviço genérica que busque os dados. As especificações de busca para cada entidade variam de acordo com a implementação.
O diagrama de classes abaixo resume o modelo:


Implementando o diagrama

Pessoa.java
public class Pessoa {
    
    private String nome;
    private int idade;
    private Date nascimento;

    //métodos getters & setters
}

GenericService.java
public interface GenericService<T> {
    //a quantidade de registros que serão carregados
    List<T> buscaPaginada(int inicio, int fim);
    //a quantidade de registros na fonte de dados
    int countLinhas();
}
PessoaService
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class PessoaService implements GenericService<Pessoa>{

    //representando o banco de dados
    private List<Pessoa> dataSource;
    
    public PessoaService() {

        dataSource = new ArrayList<>();
        for(int i = 0; i < 100; i++){
            Pessoa p = new Pessoa();
            p.setNome("Pessoa "+i);
            p.setIdade(i);
            p.setNascimento(new Date());
            dataSource.add(p);
        }        
    }    

    //implementação para a entidade pessoa
    //as regras podem variar de entidade para entidade...
    @Override
    public List<Pessoa> buscaPaginada(int inicio, int fim) {        

        return dataSource.subList(inicio, fim);
    }

    @Override
    public int countLinhas() {

        return dataSource.size();
    }    
}

Nossa implementação de GenericLazyDataTable. Repare que o tipo do objeto é genérico (T), ou seja, a princípio não se sabe qual é o tipo de objeto sendo buscado nem qual é o critério de busca, que
dependerá da implementação fornecida para a interface GenericService.

import java.util.List;
import java.util.Map;
import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;

public class GenericLazyDataTable<T> extends LazyDataModel<T>{
        
    private final GenericService genericService;

    public GenericLazyDataTable(GenericService genericService) {

        this.genericService = genericService;
    }

    @Override
    public List<T> load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {

        int linhas = genericService.countLinhas();
        this.setRowCount(linhas);
        return genericService.buscaPaginada(first, first + pageSize);
    }    
}

O managed bean controller da página JSF
import java.io.Serializable;
import javax.annotation.PostConstruct;
import javax.faces.view.ViewScoped;
import javax.inject.Named;

@Named
@ViewScoped
public class ManagedBean implements Serializable{
    
    private GenericLazyDataTable genericLazyDataTable;
    private GenericService genericService;    

    @PostConstruct
    public void init(){        

        genericService = new PessoaService();
        genericLazyDataTable = new GenericLazyDataTable(genericService);
    }

    public GenericLazyDataTable getGenericLazyDataTable() {

        return genericLazyDataTable;
    }        
}

E a página inicial index.xhtml

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:p="http://primefaces.org/ui"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>Lazy Pessoa</title>
    </h:head>

    <h:body>
        <p:dataTable value="#{managedBean.genericLazyDataTable}" var="pessoa" lazy="true" rows="20" paginator="true">

            <p:column headerText="nome">
                <h:outputText value="#{pessoa.nome}" />
            </p:column>

            <p:column headerText="idade">
                <h:outputText value="#{pessoa.idade}" />
            </p:column>

            <p:column headerText="nascimento">
                <h:outputText value="#{pessoa.nascimento}" >
                    <f:convertDateTime pattern="dd/MM/yyyy" locale="pt" />
                </h:outputText>
            </p:column>

        </p:dataTable>
    </h:body>
</html>

Estrutura do projeto na IDE NetBeans 8.1 (utilizando Maven)


Tornando a implementação ainda mais genérica

É possível tornar esse modelo ainda mais genérico. Por exemplo poderíamos criar um campo Map em GenericLazyDataTable e sobrecarregar o método buscaPaginada de GenericService para definir filtros de busca. Algo como: 
private Map<String, Object> filtrosPersonalizados;

//outros campos...

//pode ser chamado pelo Managed Bean
public void adicionarFiltro(String nomeDocampo, Object tipoDoCampo) {

        filtrosPersonalizados.put(campo, object);
}

No comments:

Post a Comment