DataTable - Lazy Loading com Primefaces
Autor: Rodrigo Menezes | Publicado: 2013-05-13T07:40:00-07:00

Um problema bem comum com quem trabalha com aplicações é web são as grids com paginação em memória. Para uma consulta que não retorna muitos dados o problema não é muito grande, mas em um sistema em produção é muito&nbsp;provável&nbsp;que paginar em memória se torne um gargalo rapidamente.

O Seam criou uma solução bastante elegante e prática que lida bem com filtros dinâmicos. Com base em minha experiencia com o Seam, decidi implementar&nbsp;algo com funcionalidade parecida com JSF2 e Primefaces.

O DataTable do Primefaces suporta Lazy Loading&nbsp;através da classe&nbsp;tipada&nbsp;org.primefaces.model.LazyDataModel, mas o exemplo deles é feito em cima de uma lista pronta em vez de um consulta EJB-QL. Vamos utilizar os&nbsp;métodos&nbsp;setFirstResult e&nbsp;setMaxResults para trazer o range correto da consulta de acordo com a paginação.

Para permitir paginação é preciso saber o total de elementos que a consulta traria e para isto será utilizado uma consulta com count. Na solução do Seam esta consulta é criada automaticamente e funciona bem para a maioria dos casos, mas é&nbsp;possível criar manualmente a consulta. Para simplificar as coisas nesta primeira versão, a consulta do count deve ser criada manualmente.

Para os filtros o&nbsp;funcionamento&nbsp;vai ser semelhante ao do Seam, mas sem a restrição de permitir apenas uma expressão el por filtro.

A classe abstrata tipada&nbsp;DataList&nbsp;estende&nbsp;a&nbsp;LazyDataModel fazendo todo o tratamento. Criei a classe como abstrata para que seja&nbsp;obrigatória a implementação dos&nbsp;métodos&nbsp;abaixo:

protected Map&lt;String, DataListFilter&gt; getFiltros();
protected abstract String getSqlList();
protected abstract String getSqlCount();


O&nbsp;principal&nbsp;método da classe LazyDataModel &nbsp;é o&nbsp;public List load(int first, int pageSize, String sortField, SortOrder sortOrder, Map filters). É&nbsp;necessário&nbsp;sobrescrever, pois ele sempre retorna&nbsp;UnsupportedOperationException("Lazy loading is not implemented.").

O&nbsp;DataTable do Primefaces possui sistema de ordenação e de filtros e eles são repassados por esses&nbsp;parâmetros, juntamente com os dados referentes a paginação:
int first - Equivalente ao OFFSET
int pageSize - Equivalente ao LIMIT
String sortField - Nome do campo usado na ordenação
SortOrder sortOrder - Enum que indica se a ordenação será ASC ou DESC
Map filters - Nessa map a key indica o nome do campo e o value o valor.

Como o sistema de filtro do método load suporta apenas Strings, fiz um suporte para filtros com valores dinâmicos baseados em 'el'.

Para definir o filtro é preciso apenas sobrescrever o&nbsp;método&nbsp;getFiltros() como pode ser visto no exemplo abaixo.
 
 protected Map&lt;String, DataListFilter&gt; getFiltros() {
  filtros = new HashMap&lt;String, DataListFilter&gt;();
  filtros.put("nome", new DataListFilter("AND o.nome like #{empty estadoDataList.nomeEstado ? null : estadoDataList.nomeEstado.concat('%')}"));
  filtros.put("sigla", new DataListFilter("AND o.sigla = #{estadoDataList.siglaEstado}"));
  return filtros;
 }


A classe DataListFilter será a&nbsp;responsável&nbsp;pelas operações em cima do filtro. Basicamente ela precisa fazer o parse do filtro para encontrar todas as expressões #{el} e&nbsp;transformar&nbsp;em um&nbsp;parâmetro da Query. Por exemplo&nbsp;para o filtro "AND o.sigla = #{estadoDataList.siglaEstado}" será encontrada a el #{estadoDataList.siglaEstado} e a consulta EJB-QL será AND o.sigla = :estadoDataList_siglaEstado.

Outra coisa importante é avalizar todas as expressões do filtro e caso todas retornem valor o método&nbsp;isPopulatedExpressions() retornará true. Isto é usado pela classe DataList para saber se o filtro deve ser adicionado na String da Query bem como no momento de aplicar os parâmetros.

O regex que identifica e captura as expressões é bastante simples e utiliza grupos para poder obter as informações de maneira mais fácil: (\#\{(.*?)\})

Para avaliar as expressões criei o&nbsp;método&nbsp;abaixo, ele é bastante&nbsp;útil&nbsp;em uma aplicação JSF:
 
 private &lt;C&gt; C eval(String el, Class&lt;C&gt; classReturn) {
  FacesContext facesContext = FacesContext.getCurrentInstance();
  Application application = facesContext.getApplication();
  ExpressionFactory factory = application.getExpressionFactory();
  ELContext elContext = facesContext.getELContext();
  ValueExpression valueExpression = factory.createValueExpression(elContext, el, classReturn);
  return (C) valueExpression.getValue(elContext);
 }



Na pratica a criação do ManagedBean referente do DaraTable fica simples:
 
package br.rodrigo.dao.list;

import java.util.HashMap;
import java.util.Map;

import javax.enterprise.context.RequestScoped;
import javax.faces.bean.ManagedBean;
import javax.inject.Inject;
import javax.persistence.EntityManager;

import br.rodrigo.dao.DataList;
import br.rodrigo.dao.DataListFilter;
import br.rodrigo.model.Estado;

@ManagedBean
@RequestScoped
public class EstadoDataList extends DataList&lt;Estado&gt; {
 
 private static final String SQL_LIST = "select o from Estado o";
 private static final String SQL_COUNT = "select count(o) from Estado o";
 
 private Map&lt;String, DataListFilter&gt; filtros;
 
 private String nomeEstado;
 private String siglaEstado;
 
 @Inject
 private EntityManager em;

 private static final long serialVersionUID = 1L;

 public String getNomeEstado() {
  return nomeEstado;
 }
 
 public void setNomeEstado(String nomeEstado) {
  this.nomeEstado = nomeEstado;
 }
 
 public String getSiglaEstado() {
  return siglaEstado;
 }
 
 public void setSiglaEstado(String siglaEstado) {
  this.siglaEstado = siglaEstado;
 }

 @Override
 protected Map&lt;String, DataListFilter&gt; getFiltros() {
  filtros = new HashMap&lt;String, DataListFilter&gt;();
  filtros.put("nome", new DataListFilter("AND o.nome like #{empty estadoDataList.nomeEstado ? null : estadoDataList.nomeEstado.concat('%')}"));
  filtros.put("sigla", new DataListFilter("AND o.sigla = #{estadoDataList.siglaEstado}"));
  return filtros;
 }

 @Override
 protected String getSqlList() {
  return SQL_LIST;
 }

 @Override
 protected String getSqlCount() {
  return SQL_COUNT;
 }
}


No arquivo xhtml o código ficaria:





Fiz esse mecanismo como prova de conceito e&nbsp;exercício, embora tudo esteja funcional é preciso trabalhar melhor as classes. Abaixo vocês podem fazer o download das classes e do projeto eclipse. Utilizei como servidor o Jboss 7.0.1 final e como IDE o Eclipse Juno e Jboss tools instalado via Eclipse Market.

DataList
DataListFilter
EstadoDataList
StringUtil
ScriptBanco
Página com a grid

Projeto