Hace ya un par de años que ando metido en proyectos que usan el framework de Spring. Recientemente, por necesidades del proyecto he necesitado utilizar la paginación. Spring Data nos proporciona mejoras de rendimiento y fiabilidad para nuestra aplicación. Así es como podemos integrarla.
Para que sirve eso de paginar una consulta
Cuando lanzamos una consulta a la base de datos para que nos devuelva un conjunto de objetos, como por ejemplo.
select a from Actor a
Spring data devolverá todas las entradas de dicha tabla, independientemente del como lo mostremos nosotros posteriormente. Es decir, es relativamente sencillo hacer que nuestra aplicación muestre solo 5 elementos de la lista la primera vez, y al darle a siguiente muestre los otros 5. Pues bien, aunque hagamos una paginación visual, realmente estaremos extrayendo todo el conjunto de datos de la bbdd aunque el usuario solo cargue los primeros 5 elementos. Por ello, es fundamental el uso de la paginación que Spring Data nos proporciona.
El uso de esta herramienta, reduce considerablemente el tiempo de consulta, teniendo que extraer solo X valores de la BBDD. Lo que se traduce en una mejora de rendimiento bastante apreciable. Sin embargo, también tiene sus desventajas y es que al tener que hacer varias consultas al servidor, el control de concurrencia puede darnos problemas. Imaginaos que hay varios usuarios tocando los datos ademas de nosotros, en alguna de esas peticiones puede darse la situación de que extraemos un dato incorrecto, o simplemente un dato que no existe.
Como ya he comentado antes, Spring Data nos brinda soporte completo para esta paginación. Crea toda la lógica para implementarlo, como por ejemplo el número de filas y páginas que queremos mostrar. Hacer la implementación es tremendamente sencillo, solamente tendremos que tocar en un par de sitios de nuestra aplicación.
- En tu repositorio personalizado, extendiendo a PagingAndSortingRepository
- En el servicio que implementa dicho repositorio. Creando un objeto PageRequest que implementa la interfaz Pageable. Este objeto PageRequest incluye el número de página y el tamaño de la misma. Además, también permite opciones de ordenación que puedes consultar aquí
Implementación
Comenzamos creando un repositorio que extienda a PagingAndSortingRepository
@Repository
public interface TripRepository extends JpaRepository<Trip, Integer>, PagingAndSortingRepository<Trip, Integer> {
@Query("select c.audits from Trip c where c.id = ?1")
Collection<Audit> getAuditByTrip(int tripId);
@Query("select c.notes from Trip c where c.id = ?1")
Collection<Note> getNotesByTrip(int tripId);
@Query("select c.relatedTrips from Category c where c = ?1")
Collection<Trip> tripsGivenACategory(Category category);
}
Simplemente con llevar a cabo esta acción, nuestro repositorio ya tendrá disponible dicha funcionalidad.
Nos vamos ahora a nuestro servicio que implementa dicho repositorio, e implementamos el siguiente método.
@Service
@Transactional
public class TripService {
// Constructors--------------------------------------------------------------------------------------
@Autowired
private TripRepository tripRepository;
// Managed repository--------------------------------------------------------------------------------
public TripService() {
super();
}
public Iterable<Trip> tripByPage(int pageNumber, int pageSize){
PageRequest pageRequest = new PageRequest(pageNumber,pageSize);
Iterable<Trip> res = tripRepository.findAll(pageRequest);
return res;
}
}
Como podemos observar nuestro método tripByPage tiene como argumentos el número de página a extraer y el número de elementos de cada página (para el ejemplo, el tamaño de página lo pondremos como variable, pero lo más normal es que sea un constante ya predefinida) . Lo único que tenemos que hacer es construir el PageRequest con los valores dados. Posteriormente, utilizar el método Crud findAll con el parámetro PageRequest. Esto lanzará una consulta a la base de datos y nos devolverá un resultado paginado. Este resultado podemos guardarlo en un Iterable<T>
Testeo
Ahora solo tenemos que comprobar que los resultados se devuelven correctamente, y que si cambiamos los parámetros obtendremos valores diferentes. Para ello vamos a construir un test JUnit para llevar a cabo dicha prueba.
public void tripsByPage() throws Exception {
//Todos los items de la base de datos
Collection<Trip> allTrips = tripService.findAll();
for(Trip t: allTrips){
System.out.println(t);
}
System.out.println("");
//Item paginados
Iterable<Trip> res = tripService.tripByPage(0,2);
for(Trip t: res){
System.out.println(t);
}
}
La ejecución de esta prueba devolverá por un lado todos los ítems de la base de datos, y por otro lado, los mismos items ya paginados. Tras ejecutar obtenemos:
domain.Trip{id=345, version=0}
domain.Trip{id=403, version=0}
domain.Trip{id=341, version=0}
domain.Trip{id=345, version=0}
La paginación devuelve los 2 primeros elementos, ya que hemos especificado que la página solo contiende dos elementos. Veamos que pasa si consultamos la siguiente página cambiando el índice.
Iterable<Trip> res = tripService.tripByPage(1,2);
domain.Trip{id=403, version=0}
Devuelve el Trip que queda por mostrar, luego, nuestra implementación ha funcionado correctamente. Tenemos que tener presente que PageRequest funciona con indexación-cero, es decir, la primera página se encuentra en el índice 0, no en el 1.
Como hemos podido comprobar, la implementación de la paginación es algo tremendamente sencillo y ventajoso ya que nos va a permitir mejorar considerablemente el rendimiento de nuestra aplicación con tan solo unas pocas líneas de código.