Arquivo

Posts Tagged ‘paginação’

Implementação de uma Busca Paginada com Primefaces e JPA

Paginar é limitar a quantidade de itens exibidos em uma página e fornecer uma forma de acessar os demais itens e paǵinas em geral através de links, que são os chamados paginadores – primeira página, página anterior, próxima página, última página e quantidade de itens por página.

A paginação pode ocorrer no cliente ou no servidor, mas é mais comum que ocorra no servidor, pois lá temos mais opções para investir na diminuição do tempo de carregamento inicial de páginas que permitem acesso a uma grande quantidade de dados. Como evitaremos o carregamento e o processamento de objetos desnecessáriamente, haverá melhoria na experiência do usuário e diminuição do consumo de CPU e de memória.

Dá trabalho implementar um mecanismo de paginação do zero. Recomendo que você procure frameworks e bibliotecas para te auxiliar. Vamos ver como fazer uma busca paginada desde o acesso ao banco de dados até a apresentação na tela com o que o Primefaces e a JPA nos oferece. Considere a entidade Paciente:

@Entity
@Table(name="TBL_PACIENTE")
public class Paciente {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private int id;
   @Column(name = "NM_NOME")
   private String nome;
   @Column(name = "CL_STATUS")
   private int status;
}

Nosso DAO terá dois métodos: um para buscar os objetos e outro para buscar apenas a quantidade de itens. Você poderia utilizar a busca de objetos e verificar o tamanho da lista? Sim, mas não esqueça que nosso objetivo é o carregamento mais rápido da página. Para situações em que só um COUNT é suficiente não é necessário carregar todos os objetos para a memória para ter acesso a uma informação, que nesse caso é o total de objetos. Os métodos do nosso DAO receberão dois parâmetros: o índice da página onde está o primeiro registro a ser exibido e a quantidade de itens nessa página:

@Stateless
public class PacienteDao extends AbstractDao {
   public List<Paciente> buscar(int firstPage, int maxResults) 
   throws Exception {
      CriteriaBuilder criteriaBuilder = 
         getEntityManager().getCriteriaBuilder();
      CriteriaQuery<Paciente> criteriaQuery = 
         criteriaBuilder.createQuery(Paciente.class);
      Root<Paciente> root = criteriaQuery.from(Paciente.class);
		
      TypedQuery<Paciente> jpaQuery = 
         getEntityManager().createQuery(criteriaQuery);

      if (maxResults > 0) {
         jpaQuery = jpaQuery.setMaxResults(maxResults);
      }
   
      jpaQuery = jpaQuery.setFirstResult(firstPage);

      return jpaQuery.getResultList();
   }

   public Long contar(int firstPage, int maxResults) throws Exception {
      CriteriaBuilder criteriaBuilder = 
         getEntityManager().getCriteriaBuilder();
      CriteriaQuery<Long> root = builder.createQuery(Long.class);
      Root<Paciente> from = root.from(Paciente.class);
      root.select(criteriaBuilder.count(from));
      TypedQuery<Long> jpaQuery = getEntityManager().createQuery(root);

      if (maxResults > 0) {
         jpaQuery = jpaQuery.setMaxResults(maxResults);
      }
   
      jpaQuery = jpaQuery.setFirstResult(firstPage);

      return jpaQuery.getSingleResult();
   }
}

Agora vamos passar para a implementação de um modelo de lista paginada que é carregado sob demanda. Criei uma classe que estende LazyDataModel e encapsula uma lista de pacientes preenchida com valores vindos do DAO:

import org.primefaces.model.LazyDataModel;
import org.primefaces.model.SortOrder;

public class LazyPacienteDataModel extends LazyDataModel<Paciente> {

   private PacienteDao dao;

   private List<Paciente> datasource;

   public LazyPacienteDataModel(PacienteDao dao) throws Exception {
      this.dao = dao;
      Long quantidade = service.contar(0, 0);
      this.setRowCount(quantidade.intValue());
   }

   @Override
   public Paciente getRowData(String rowKey) {
      for (Paciente entidade : datasource) {
         if (entidade.getId().equals(rowKey))
             return entidade;
         }
         return null;
      }
   }

   @Override
   public Object getRowKey(Paciente entidade) {
      return entidade.getId();
   }

   @Override
   public List<Paciente> load(int first, int pageSize, String sortField, 
   SortOrder sortOrder, Map<String, Object> filters) {
      this.setPageSize(pageSize);
      datasource = dao.buscar(filtro);
      Long quantidade = dao.contar(first, pageSize);
      this.setRowCount(quantidade.intValue());
   }
}

Note que um dos parâmetros do método load() é do tipo SortOrder. Esse parâmetro é atualizado pelos componentes do Primefaces que fazem uso de LazyDataModel e ele permite informar se a ordenação dos itens deve ser ascendente ou descendente, mas não para esse exemplo isso não tem importância.

O managed bean deve manter uma referência para um LazyDataModel, cujo estado deve permanecer entre as invocações. O ViewScoped é o ideal para isso:

@ManagedBean(name="pacienteMB")
@ViewScoped
public class PacienteMB{

   @EJB
   private PacienteDao dao;

   // getter e setters

   private LazyDataModel lazyModel;

   public void buscar() {
      try {
         lazyModel = new LazyPacienteDataModel(dao);
      } catch (Exception e) {
          e.printStackTrace();
      }
   }
}

Vamos apresentar inicialmente 10 objetos em uma tabela. Esse componente de tabela do Primefaces traz um atributo chamado paginatorTemplate que permite configurarmos os paginadores que darão acesso aos demais itens. Com pouca configuração poderíamos utilizar o componente para ordenar os objetos de forma crescente ou decrescente pelo nome do paciente ou fazer filtros sobre os resultados em conjunto com a paginação, mas vamos focar na paginação:

<p:dataTable id="paciente-table" var="paciente" 
   value="#{pacienteMB.lazyModel}"
 paginator="true" paginatorTemplate="{FirstPageLink} {PreviousPageLink} 
 {PageLinks} {NextPageLink} {LastPageLink} {RowsPerPageDropdown}"
 rowsPerPageTemplate="10,20,30" rows="10" lazy="true" 
 emptyMessage="Nenhum paciente encontrado.">
   <p:column headerText="Nome">
      <h:outputText value="#{paciente.nome}" />
   </p:column>
</p:dataTable>