terça-feira, 29 de março de 2016

Paginação de dados com Hibernate e Primefaces

     Esse post sobre paginação com hibernate, considero que você já tenha seu DAO, ManagedBean, o Bean e a página XHTML feita, e quer apenas mudar a forma como os dados são trazidos do banco, melhorando dessa forma o desempenho do sistema.

Para fazermos paginação com o Hibernate, precisamos preparar a camada View (Páginas XHTML) para lidar com essa paginação. No caso do primefaces, isso é bem simples. Vamos analisar como fica o componente <p:dataTable> de forma a trabalhar com a paginação:

<p:dataTable id="carroTable" paginator="true" rows="20" lazy="true" paginatorAlwaysVisible="false" paginatorPosition="bottom" var="carro" value="#{pesquisaCarroBean.lazyCarros}" rowsPerPageTemplate="10,20,50"
emptyMessage="Nenhum carro encontrado." style="margin-top: 20px" >

      ...

<p:dataTable>

         A propriedade paginator=”true” indica que haverá um paginação, e mesmo que não fizéssemos a parte de paginação na camada  Model, o primefaces conseguiria paginar os dados trazidos para esse dataTable, porém, mesmo a exibição estando paginada, todos os dados estariam carregados em memória. Daí a importância de fazermos realmente a paginação dos dados, mudando a forma como esses dados são trazidos do banco de dados, que é o que vamos ver mais a frente nesse post.

         A propriedade paginatorAlwaysVisible=”false” serve para que o paginador aparece apenas quando houver dados suficientes para serem paginados. O paginatorPosition=”bottom” informa a posição que a paginação será exibida. A propriedade rows=”20” indica quantas linhas serão exibidas por default, já a propriedade rowsPerPageTemplate=”10,20,50” é responsável por permitir a customização da quantidade de linhas visualizadas.

         Porém, a propriedade lazy=”true” é quem realmente faz o componente do primefaces entender que haverá uma paginação real dos dados. Com isso, podemos passar para a camada Model.

       Precisamos criar uma classe responsável por fazer o meio de campo entre o componente do primefaces que será paginado e o método responsável por buscar a consulta paginada usando o hibernate. Vamos começar criando a classe LazyCarro, que criei dentro de um novo pacote chamado lazy_model dentro da minha hierarquia de pacotes. Segue o código:

public class LazyCarro extends LazyDataModel<Carro> implements Serializable{

      private static final long serialVersionUID = 1L;
     
      private CarroDAO carroDAO;
     
      public LazyCarro(CarroDAO carroDAO) {
            this.carroDAO = carroDAO;
      }
     
      @Override
      public List<Carro> load(int first, int pageSize,
                  String sortField, SortOrder sortOrder, Map<String, Object> filters) {
           
           
            List<Carro> carros = carroDAO.buscarTodosPaginado(first, pageSize);
           
            this.setRowCount(carroDAO.pegarQuantidadeDeCarros().intValue());
           
            return carros;
      }
     
}

        Essa classe estende a classe LazyDataModel do primefaces, e tem uma implementação bem simples. Preste bastante atenção quanto ao método load que deve ser sobrescrito, que deve ser o segundo e não o primeiro. Note que os dois métodos que chamamos do carroDAO (buscarTodosPaginado(first, pageSize) e pegarQuantidadeDeCarros()) precisam ser criados, então vamos adicioná-los a classe CarroDAO.

public List<Carro> buscarTodosPaginado(int primeiroDigito, int segundoDigito){
      return em.createQuery("FROM Carro", Carro.class)
                  .setFirstResult(primeiroDigito)
                  .setMaxResults(segundoDigito)
                  .getResultList();
}

public Long pegarQuantidadeDeCarros(){
      return em.createQuery("SELECT count(c) FROM Carro c", Long.class)
.getSingleResult();
}

Agora que já temos tudo feito na camada model, vamos a camada Controller que é responsável por fazer a ligação entre a View e a Model. Na classe PesquisaCarroBean vamos adicionar o objeto do tipo LazyCarro que será inicializado no método init que possui a anotação @PostConstruct e também o seu método getter para que a página XHTML possa ter acesso.

@ManagedBean
@ViewScoped
public class PesquisaCarroBean implements Serializable{
private LazyCarro lazyCarros;
      ...
public LazyCarro getLazyCarros() {
      return lazyCarros;
}
      ...
@PostConstruct
public void init(){
      lazyCarros = new LazyCarros(new CarroDAO);
...
}
      ...
}

         Pronto, com isso já temos uma consulta paginada. Caso apareça um erro informando e o “Lazy loading não foi implementado”, sugiro que verifique o método load que você sobrescreveu na classe LazyCarro, checando os parâmetros com estes:

load(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters){...}


Qualquer dúvida, pode deixar nos comentários que tentarei ajudar no que for possível...

12 comentários:

  1. Blz Daniel. Cara, gostei bastante da sua publicação. Qual a versão do PrimeFaces e Hibernate usadas? Além do JavaEE. Estou utilizando o seguinte código para poder fazer lazy na minha DataTable e preciso que o filter dela funcione. Como poderia fazer? Segue abaixo meu código até o momento.

    View.xhtml #######
    "























    "
    #######
    @ManagedBean(name="valoresTFJBean")
    //@RequestScoped
    @ViewScoped
    public class ValoresTFJBean implements Serializable{
    private static final long serialVersionUID = 1L;

    private Valores valor = new Valores();
    private List lista = null;
    private List listaAno = null;
    private LazyDataModel lazyDataModel;

    public Valores getValor() {
    return valor;
    }
    public void setValor(Valores valor) {
    this.valor = valor;
    }
    public List getLista() {
    if(this.lista == null)
    {
    ValoresRN valoresRN = new ValoresRN();
    this.lista = valoresRN.listar();
    }
    return this.lista;
    }
    public LazyDataModel getLazyDataModel() {
    if(lazyDataModel == null){
    ValoresRN valoresRN = new ValoresRN();
    lazyDataModel = new ValoresTFJLazy(valoresRN.listar());
    }
    return lazyDataModel;
    }
    public List getListaAno() {
    if(this.listaAno ==null){
    ValoresRN valoresRN = new ValoresRN();
    this.listaAno= valoresRN.listaAno();
    }
    return this.listaAno;
    }

    public List listarPorAno(Integer ano){
    return new ValoresRN().listarPorAno(ano);
    }
    }
    ########
    LazyDataTable ############
    public class ValoresTFJLazy extends LazyDataModel {

    private List lista;

    public ValoresTFJLazy(List lista){
    this.lista = lista;
    }

    @Override
    public Valores getRowData(String rowKey) {
    for(Valores valor : lista) {
    if(valor.getCodigo().equals(rowKey))
    return valor;
    }

    return null;
    }
    @Override
    public Object getRowKey(Valores valor) {
    return valor.getCodigo();
    }
    @Override
    public List load(int first, int pageSize, String sortField, SortOrder sortOrder,
    Map filters) {
    List data = new ArrayList();

    //filter
    for(Valores valor : lista) {
    boolean match = true;

    if (filters != null) {
    for (Iterator it = filters.keySet().iterator(); it.hasNext();) {
    try {
    String filterProperty = it.next();
    Object filterValue = filters.get(filterProperty);
    String fieldValue = String.valueOf(valor.getClass().getField(filterProperty).get(valor));

    if(filterValue == null || fieldValue.startsWith(filterValue.toString())) {
    match = true;
    }
    else {
    match = false;
    break;
    }
    } catch(Exception e) {
    match = false;
    }
    }
    }

    if(match) {
    data.add(valor);
    }
    }

    //sort
    //if(sortField != null) {
    // Collections.sort(data, new LazySorter(sortField, sortOrder));
    //}

    //rowCount
    int dataSize = data.size();
    this.setRowCount(dataSize);

    //paginate
    if(dataSize > pageSize) {
    try {
    return data.subList(first, first + pageSize);
    }
    catch(IndexOutOfBoundsException e) {
    return data.subList(first, first + (dataSize % pageSize));
    }
    }
    else {
    return data;
    }
    }
    }
    ########

    ResponderExcluir
    Respostas
    1. Deixa eu ver se entendi, você quer usar lazy na propriedade do Bean e quando for usar o filtro essa propriedade precisa ser carregada? É isso?

      Excluir
    2. Seria isso: Tenho uma table que usa lazy e tem filter no cabeçalho. Se não uso lazy o filter funciona. Se uso lazy o filter não funciona. Ficou mais claro? Desculpa ae se não deixei a pergunta clara.

      Excluir
    3. Bom Yuri, quando usamos Lazy em uma propriedade de um Bean, estamos dizendo para o JPA: Não carregue essa propriedade para mim, a menos que eu ordene. Então toda vez que eu quiser trazer essa propriedade em um select, tenho que escrever um select que faça isso explicitamente dando um JOIN entre a tabela pricipal e a tabela a qual apropriedade pertence.

      Por exemplo: Se você tiver um Bean Pedido que tem uma propriedade lazy do tipo ItemPedido, na consulta você quer trazer campos de Pedido e de ItemPedido vai ter que fazer algo do tipo:
      "SELEC NEW Pedido ( p.codigo, p.cliente, i.codigo, i.descricao, i.valor, i.desconto ) FROM Pedido p INNER JOIN p.ItemPedido i WHERE p.codigo = :codigo".

      Note que nesse caso tenho que ter um construtor em Pedido preparado para receber esses parâmetros e montar os objetos e que terei que colocar um a um cada parâmetro que eu quiser trazer da consulta como uma consulta nativa.

      Excluir
    4. Obrigado Daniel. Ajudou a esclarecer o caminho.

      Excluir
  2. Valeu irmão, funcionou direitinho.
    Vc eh fera!

    ResponderExcluir
  3. de onde surgiu aquele .....em.createQuery(...... Este "em" é oq?

    ResponderExcluir
    Respostas
    1. É suposto que, no código, um objeto da classe EntityManager esteja disponível. No caso há um objeto de tal classe na variável "em".

      Excluir
    2. Exatamente, é porque no começo da postagem eu disse que considerava que você já tenha seu DAO funcionando, e um DAO implica em um EntityManager disponível.

      Excluir
  4. Poxa, eu segui tudo mas sempre diz que não esta implementado.
    “Lazy loading não foi implementado"

    ResponderExcluir
    Respostas
    1. Tem que usar o debug pra ver. Verificar se está chegando no método que executa o SQL.
      Verificar os imports e se seu DAO está instanciado corretamente.

      Excluir