Lire des données
Notions théoriques
findAll() et findById()
JpaRepository fournit deux méthodes fondamentales pour lire des données :
// Récupérer tous les articles (retourne une liste vide si aucun)
List<Article> articles = articleRepository.findAll();
// Récupérer un article par son id
Optional<Article> optArticle = articleRepository.findById(42L);
Gérer l'Optional avec orElseThrow
findById() retourne un Optional<Article> et non directement un Article. Un Optional est un conteneur qui peut contenir une valeur ou être vide. On ne doit jamais l'ignorer :
// Mauvais : peut lancer NullPointerException
Article article = articleRepository.findById(id).get(); // À éviter !
// Bien : lève une exception propre si non trouvé
Article article = articleRepository.findById(id)
.orElseThrow(() -> new RuntimeException("Article non trouvé"));
// Encore mieux : utiliser EntityNotFoundException de Jakarta
import jakarta.persistence.EntityNotFoundException;
Article article = articleRepository.findById(id)
.orElseThrow(EntityNotFoundException::new);
En Symfony/Doctrine, $repo->find($id) retourne null si non trouvé. En Java avec JPA, findById() retourne un Optional qui force à traiter le cas "absent" explicitement, ce qui rend le code plus sûr.
Requêtes dérivées (Derived Query Methods)
Spring Data JPA peut générer automatiquement des requêtes SQL à partir du nom de la méthode dans le repository. Pas besoin d'écrire du SQL !
public interface ArticleRepository extends JpaRepository<Article, Long> {
// SELECT * FROM articles WHERE titre = ?
List<Article> findByTitre(String titre);
// SELECT * FROM articles WHERE titre LIKE '%mot%'
List<Article> findByTitreContaining(String mot);
// SELECT * FROM articles WHERE date_creation > ?
List<Article> findByDateCreationAfter(LocalDate date);
// SELECT * FROM articles WHERE titre LIKE '%mot%' AND publie = true
List<Article> findByTitreContainingAndPublie(String mot, boolean publie);
// SELECT * FROM articles ORDER BY date_creation DESC
List<Article> findAllByOrderByDateCreationDesc();
}
Convention de nommage : findBy + NomAttribut + (optionnel : Containing, After, Before, GreaterThan, StartingWith...).
Requêtes personnalisées avec @Query
Pour des requêtes plus complexes, utilisez l'annotation @Query avec du JPQL (Java Persistence Query Language). JPQL ressemble à SQL mais utilise les noms de classes et d'attributs Java (pas les noms de tables/colonnes SQL) :
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface ArticleRepository extends JpaRepository<Article, Long> {
// JPQL : on écrit "Article" (classe Java) et "a.auteur.id" (attribut Java)
@Query("SELECT a FROM Article a WHERE a.auteur.id = :auteurId")
List<Article> trouverParAuteur(@Param("auteurId") Long auteurId);
// Compter les articles d'un auteur
@Query("SELECT COUNT(a) FROM Article a WHERE a.auteur.id = :auteurId")
long compterParAuteur(@Param("auteurId") Long auteurId);
}
Lorsque vous utilisez :nomParametre dans une @Query, le paramètre de la méthode doit être annoté avec @Param("nomParametre"). Sans cela, Spring ne sait pas quel paramètre correspond à :nomParametre.
Pagination avec Pageable
Pour de grands jeux de données, la pagination évite de charger toutes les lignes en mémoire :
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
// Page 0 (première page), 10 articles par page, triés par dateCreation décroissant
Pageable pageable = PageRequest.of(0, 10, Sort.by("dateCreation").descending());
Page<Article> page = articleRepository.findAll(pageable);
List<Article> articles = page.getContent(); // Les articles de cette page
int totalPages = page.getTotalPages(); // Nombre total de pages
long totalElements = page.getTotalElements(); // Nombre total d'articles
boolean estDernierePage = page.isLast();
Dans un contrôleur avec paramètre de page dans l'URL (/articles?page=2) :
@GetMapping
public String listerArticles(@RequestParam(defaultValue = "0") int page, Model model) {
Pageable pageable = PageRequest.of(page, 10, Sort.by("dateCreation").descending());
Page<Article> pageArticles = articleRepository.findAll(pageable);
model.addAttribute("pageArticles", pageArticles);
return "articles/liste";
}
Exemple pratique
Contrôleur complet avec liste et page détail :
package org.joliciel.monblog.controller;
import jakarta.persistence.EntityNotFoundException;
import org.joliciel.monblog.entity.Article;
import org.joliciel.monblog.repository.ArticleRepository;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@Controller
@RequestMapping("/articles")
public class ArticleController {
private final ArticleRepository articleRepository;
public ArticleController(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}
// Liste tous les articles
@GetMapping
public String listerArticles(Model model) {
List<Article> articles = articleRepository.findAll();
model.addAttribute("articles", articles);
return "articles/liste";
}
// Page détail d'un article
@GetMapping("/{id}")
public String afficherArticle(@PathVariable Long id, Model model) {
Article article = articleRepository.findById(id)
.orElseThrow(EntityNotFoundException::new);
model.addAttribute("article", article);
return "articles/detail";
}
}
Vue templates/articles/liste.html :
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head><title>Articles - MonBlog</title></head>
<body>
<h1>Tous les articles</h1>
<div th:if="${#lists.isEmpty(articles)}">
<p>Aucun article pour le moment.</p>
</div>
<ul>
<li th:each="article : ${articles}">
<a th:href="@{/articles/{id}(id=${article.id})}"
th:text="${article.titre}">Titre de l'article</a>
<span th:text="${#temporals.format(article.dateCreation, 'dd/MM/yyyy')}"></span>
</li>
</ul>
<a th:href="@{/articles/nouveau}">Écrire un article</a>
</body>
</html>
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Dans ce TP, vous allez afficher la liste des articles et une page de détail dans MonBlog.
Étape 1 — Afficher la liste des articles
Implémentez la méthode listerArticles dans le contrôleur et la vue associée.
Déclarez explicitement le type générique : List<Article> plutôt que var ou List. Cela rend le code plus lisible et permet à IntelliJ de détecter des erreurs de type plus tôt.
Étape 2 — Afficher le détail d'un article
N'utilisez jamais .get() directement sur un Optional. Si l'Optional est vide, .get() lance une NoSuchElementException avec un message peu informatif. Préférez .orElseThrow(EntityNotFoundException::new) qui lance une exception métier claire, que Spring peut intercepter pour afficher une page 404.
Étape 3 — Ajouter une méthode de recherche par mot-clé
Ajoutez au repository une méthode pour chercher les articles par mot-clé dans le titre.
Utilisez les requêtes dérivées (nommage de méthode) pour les cas simples. Pour des requêtes complexes (jointures multiples, agrégations), passez à @Query avec JPQL. Évitez les requêtes dérivées avec trop de mots-clés enchaînés : elles deviennent vite illisibles (findByTitreContainingAndPublieAndAuteurEmailEndingWith).