Cuando trabajamos creando APIs, y debemos de retornar una lista de datos, es muy común utilizar paginación. Por lo tanto, si tenemos miles o millones de registros, no vamos a a retornarlos todos en la misma solicitud. Si hicieramos eso, el rendimiento de la aplicación se vería comprometido.
La paginación nos provee con una forma de retornar un subset de la información que realmente necesitamos mostrar o trabajar. En Java es muy probable que estemos usando JPA para acceder a la capa de persistencia, la idea de este artículo es explicar cómo hacer la paginación usando los features del Criteria Query de JPA. El Criteria API de JPA nos permite create queries de manera programática, lo cual es muy util cuando tenemos cláusulas opcionales por la cuales filtrar como parte del where
, y que no podemos manejar de manera estática usando queries JPQL..
Comencemos
Para una estructura paginada tenemos 2 partes:
- Los datos de la página (los cuales contienes solo un subset de la información). Una página está definida por un
offset
(posición del primer registro) y unlimit
(tamaño de la página). - El número total the todos los registros del query.
Ahora si, podemos consultar la capa de persistencia (base de datos) para obtener la estructura ya mencionada.
Para obtener los datos de la página vamos a usar los métodos setMaxResults(int)
para indicar el tamaño de la página y setFirstResult(int)
para indicar la posición inicial. Desde el Criteria API, vamos a estar usando el CriteriaBuilder
para crear un objeto CriteriaQuery
, con el cual vamos a ir formando la consulta deseada.
public List<Person> findAll(int offset, int limit){ CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Person> criteriaQuery = cb.createQuery(Person.class); Root<Person> root = criteriaQuery.from(Person.class); criteriaQuery.select(root); List<Person> result = entityManager .createQuery(criteriaQuery) .setMaxResults(limit) .setFirstResult(offset) .getResultList(); return result; }
La lógica para obtener la cantidad total de registros is muy similar a la anterior. Pero con unas pequeñas diferencias, el CriteriaQuery
retornará un tipo de dato Long
, y cuando creamos el select
usaremos el CriteriaBuilder
para indicar que deseamos hacer un count
the registros. La otra diferencia es que en este caso, no aplicamos los métodos setFirstResult
y setMaxResults
.
public Long countAll(){ CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<Long> criteriaQuery = cb.createQuery(Long.class); Root<Person> root = criteriaQuery.from(Person.class); criteriaQuery.select(cb.count(root)); Long result = entityManager.createQuery(criteriaQuery).getSingleResult(); return result; }
Ahora bien, ese ejemplo es bastante plano y sencillo, es muy probable que necesitemos filtrar datos en nuestro Query. El API Criteria permite hacer eso creando un arreglo de Predicados y agregarlos al where
del objeto CriteriaQuery
. Tal como podemos observar, seguimos usando el CriteriaBuilder
para crear los predicados.
List<Predicate> predicatesList = new ArrayList<>(); predicatesList.add(cb.equal(root.get("status"), status)); if (status != null) { predicatesList.add(cb.equal(root.get("lastName"), lastName)); } Predicate[] finalPredicates = new Predicate[predicatesList.size()]; predicatesList.toArray(finalPredicates); criteriaQuery.where(finalPredicates);