
Pagination et tri (`Pageable`, `Sort`)
Maîtrisez la pagination et le tri dans Spring Data JPA en utilisant les interfaces Pageable et Sort. Récupérez des données par pages et ordonnez-les facilement.
Gérer les grands ensembles de données : le besoin de pagination et de tri
Lorsque vos tables de base de données contiennent un grand nombre d'enregistrements (des milliers, voire des millions), récupérer toutes les données en une seule fois est souvent impraticable et inefficace. Cela peut entraîner une consommation excessive de mémoire dans votre application, une charge importante sur la base de données, et des temps de réponse lents pour l'utilisateur final. Afficher des milliers de lignes dans une interface utilisateur n'est de toute façon pas convivial.
La solution standard à ce problème est la pagination : récupérer et afficher les données par petits morceaux (pages). Il est également essentiel de pouvoir trier ces données selon des critères spécifiques (par nom, par date, par prix, etc.) pour permettre aux utilisateurs de trouver facilement l'information qu'ils recherchent.
Spring Data fournit une abstraction élégante et puissante pour gérer à la fois la pagination et le tri de manière cohérente et simple, principalement via deux interfaces clés : org.springframework.data.domain.Sort et org.springframework.data.domain.Pageable.
Ordonner les résultats : l'interface `Sort`
L'interface Sort encapsule les informations de tri à appliquer à une requête. Elle vous permet de spécifier sur quelle(s) propriété(s) de l'entité vous souhaitez trier et dans quel ordre (ascendant ou descendant).
Vous créez généralement une instance de Sort en utilisant ses méthodes statiques `by()` :
import org.springframework.data.domain.Sort;
// Tri simple par la propriété 'name' en ordre ascendant (par défaut)
Sort sortByNameAsc = Sort.by("name");
// Tri par la propriété 'price' en ordre descendant
Sort sortByPriceDesc = Sort.by(Sort.Direction.DESC, "price");
// Tri par plusieurs propriétés : d'abord par 'category' ascendant, puis par 'name' descendant
Sort sortByMultiple = Sort.by("category").ascending()
.and(Sort.by("name").descending());
// Alternative pour plusieurs propriétés
Sort sortByMultipleAlt = Sort.by(Sort.Order.asc("category"), Sort.Order.desc("name"));
Une fois que vous avez un objet Sort, vous pouvez le passer en argument à certaines méthodes des repositories Spring Data. La méthode findAll(Sort sort), héritée de JpaRepository (ou PagingAndSortingRepository), est l'exemple le plus direct :
@Autowired
private ProductRepository productRepository;
public List findProductsSortedByName() {
Sort sort = Sort.by("name");
return productRepository.findAll(sort);
}
public List findProductsSortedByPriceDesc() {
Sort sort = Sort.by(Sort.Direction.DESC, "price");
return productRepository.findAll(sort);
}
Spring Data ajoutera automatiquement la clause ORDER BY appropriée à la requête SQL générée. Vous pouvez également utiliser Sort avec des méthodes de requête dérivées ou des requêtes @Query (bien que pour ces dernières, l'intégration de Sort dynamique puisse nécessiter des ajustements dans la requête JPQL/SQL).
Récupérer les données par pages : l'interface `Pageable`
L'interface Pageable est l'abstraction centrale pour la pagination. Elle combine les informations de pagination (quelle page demander et combien d'éléments par page) avec les informations de tri (un objet Sort).
L'implémentation la plus courante de Pageable est la classe org.springframework.data.domain.PageRequest. Vous la créez en spécifiant le numéro de la page (attention, basé sur 0), la taille de la page, et éventuellement un objet Sort :
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
// Demander la première page (index 0), avec 10 éléments, trié par nom ascendant
Pageable firstPageWithTenElementsSortedByName = PageRequest.of(0, 10, Sort.by("name"));
// Demander la troisième page (index 2), avec 20 éléments, sans tri spécifique (ou tri par défaut du SGBD)
Pageable thirdPageWithTwentyElements = PageRequest.of(2, 20);
// Demander la première page avec 5 éléments, trié par prix descendant
Sort sortByPriceDesc = Sort.by(Sort.Direction.DESC, "price");
Pageable firstPageWithFiveSortedByPrice = PageRequest.of(0, 5, sortByPriceDesc);
Comme pour Sort, vous pouvez ajouter un paramètre de type Pageable à vos méthodes de repository. Cela fonctionne avec findAll, les méthodes dérivées, et les méthodes annotées avec @Query.
public interface ProductRepository extends JpaRepository {
// Méthode dérivée supportant la pagination et le tri
Page findByCategory(String category, Pageable pageable);
}
// Utilisation dans un service
@Autowired
private ProductRepository productRepository;
public Page findProductsByCategoryPaginated(String category, int page, int size) {
// Créer le Pageable
Pageable pageRequest = PageRequest.of(page, size, Sort.by("name"));
// Appeler la méthode du repository
return productRepository.findByCategory(category, pageRequest);
}
Traiter le résultat : l'interface `Page`
Lorsque vous utilisez un paramètre Pageable dans une méthode de repository, le type de retour attendu est souvent org.springframework.data.domain.Page (où T est votre type d'entité). L'objet Page est très utile car il contient non seulement la liste des éléments pour la page demandée, mais aussi des métadonnées essentielles sur la pagination :
getContent(): Retourne laListdes entités pour la page courante.getTotalElements(): Retourne le nombre total d'éléments disponibles sur toutes les pages (exécute une requête de comptage supplémentaire).getTotalPages(): Retourne le nombre total de pages disponibles.getNumber(): Retourne le numéro de la page courante (base 0).getSize(): Retourne la taille de la page demandée.getNumberOfElements(): Retourne le nombre d'éléments dans la page courante (peut être inférieur à `getSize()` sur la dernière page).getSort(): Retourne l'objet `Sort` utilisé pour cette requête.isFirst(): Retourne `true` si c'est la première page.isLast(): Retourne `true` si c'est la dernière page.hasNext(): Retourne `true` s'il y a une page suivante.hasPrevious(): Retourne `true` s'il y a une page précédente.
// Suite de l'exemple précédent
public void displayPaginatedProducts(String category) {
int currentPage = 0;
int pageSize = 10;
Page productPage;
do {
productPage = findProductsByCategoryPaginated(category, currentPage, pageSize);
System.out.println("--- Page " + (productPage.getNumber() + 1) + " / " + productPage.getTotalPages() + " ---");
System.out.println("Nombre total d'éléments: " + productPage.getTotalElements());
List productsOnPage = productPage.getContent();
for (Product p : productsOnPage) {
System.out.println(" - " + p.getName() + " (" + p.getPrice() + ")");
}
currentPage++;
} while (productPage.hasNext());
}
Ces informations sont cruciales pour construire des contrôles de pagination dans une interface utilisateur (afficher les numéros de page, les boutons "Précédent"/"Suivant", etc.).
Intégration avec Spring Web MVC
L'une des grandes forces de Spring Boot est l'intégration transparente entre ses différents modules. Spring Web MVC peut automatiquement créer et injecter des objets Pageable (et Sort) comme paramètres dans vos méthodes de contrôleur, en se basant sur des paramètres de la requête HTTP.
Par défaut, les paramètres attendus sont :
page: Le numéro de la page (base 0).size: Le nombre d'éléments par page.sort: La propriété (ou les propriétés) de tri, suivie éventuellement par,ascou,desc. Exemple :sort=name,descousort=category,asc&sort=name,desc(pour plusieurs tris).
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class ProductWebController {
@Autowired
private ProductService productService; // Un service qui utilise le repository
@GetMapping("/products")
public String listProducts(@RequestParam(required = false) String category,
Pageable pageable, // Injecté automatiquement !
Model model) {
Page productPage;
if (category != null) {
productPage = productService.findProductsByCategoryPaginated(category, pageable);
} else {
productPage = productService.findAllProductsPaginated(pageable);
}
model.addAttribute("productPage", productPage);
model.addAttribute("category", category); // Pour maintenir le filtre
// Les informations de pageable (page, size, sort) sont aussi implicitement disponibles
return "product-list"; // Nom de la vue (ex: Thymeleaf)
}
}
Si un utilisateur accède à /products?page=1&size=5&sort=price,desc, Spring MVC créera automatiquement un objet Pageable correspondant (page 1, taille 5, tri par prix descendant) et l'injectera dans la méthode `listProducts`.
Vous pouvez personnaliser les noms de ces paramètres et les valeurs par défaut (par exemple, la taille de page par défaut) via la configuration globale de Spring MVC ou en utilisant l'annotation @PageableDefault sur le paramètre du contrôleur.
Alternatives : `Slice` et `List`
Si vous n'avez pas besoin de connaître le nombre total d'éléments (ce qui évite la requête de comptage supplémentaire, potentiellement coûteuse sur de très grandes tables), vous pouvez utiliser org.springframework.data.domain.Slice comme type de retour au lieu de Page. Un Slice contient les éléments de la tranche actuelle et sait simplement s'il existe une tranche suivante (hasNext()). C'est une option plus légère lorsque seule la navigation "avant" est nécessaire.
Si vous utilisez Pageable uniquement pour limiter le nombre de résultats ou appliquer un tri, mais que vous n'avez besoin ni du nombre total d'éléments ni de savoir s'il y a une page suivante, vous pouvez simplement retourner une java.util.List. Spring Data appliquera quand même les clauses LIMIT/OFFSET et ORDER BY correspondantes à la requête.
Conclusion : élégance et performance pour l'accès aux données
La pagination et le tri sont des fonctionnalités indispensables pour toute application manipulant des volumes de données significatifs. Spring Data, avec ses interfaces Sort, Pageable et Page (ou Slice), offre une solution remarquablement élégante, cohérente et facile à utiliser pour implémenter ces fonctionnalités.
En intégrant ces concepts directement dans les interfaces Repository et en s'intégrant de manière transparente avec Spring Web MVC, Spring Data simplifie considérablement le développement, améliore les performances en évitant de charger des données inutiles, et contribue à une meilleure expérience utilisateur. Maîtriser Pageable et Sort est donc une compétence essentielle pour tout développeur travaillant avec Spring Data JPA.