Bonjour ! Dans la leçon précédente, nous avons vu la puissance des requêtes dérivées de Spring Data JPA. Vous avez appris à construire des requêtes en nommant simplement des méthodes, ce qui est extrêmement productif pour les opérations courantes. Cependant, nous avons conclu sur leur limite : pour des requêtes complexes, les noms de méthodes peuvent devenir très longs et illisibles.
Aujourd'hui, nous allons franchir une nouvelle étape pour prendre le contrôle total de nos requêtes. L'objectif est d'écrire des requêtes personnalisées complexes et sécurisées avec JPQL et l'annotation @Query. Cette compétence est indispensable lorsque la logique d'accès aux données dépasse ce que les requêtes dérivées peuvent offrir de manière élégante.
Votre expérience avec les ORM et le SQL vous sera très utile. Nous n'allons pas écrire du SQL directement, mais utiliser son proche cousin orienté objet : le JPQL.
1. Introduction à JPQL et l'annotation @Query
Lorsque les requêtes dérivées ne suffisent plus, Spring Data JPA nous offre une porte de sortie puissante et explicite : l'annotation @Query. Elle vous permet d'associer une requête directement à une méthode de votre interface Repository.
Cette requête est généralement écrite en JPQL (Java Persistence Query Language).
Quelle est la différence entre JPQL et SQL ?
- SQL opère sur des tables et des colonnes dans la base de données.
- JPQL opère sur des entités et leurs attributs dans votre code Java.
Par exemple, pour sélectionner des utilisateurs par email :
- SQL :
SELECT * FROM users WHERE email = ? - JPQL :
SELECT u FROM User u WHERE u.email = :email
Remarquez que JPQL utilise le nom de la classe (User) et de ses champs (email), pas les noms de la table et de la colonne. C'est ce qui le rend portable entre différentes bases de données.
Pour une introduction formelle à la syntaxe, l'article suivant est excellent.
JPA avec Spring Data - David Gayerie
Pour commencer, lisons une explication claire et concise de l'annotation @Query. Cet extrait de l'article de David Gayerie, que nous avons déjà consulté, va droit au but.
Lisez la section intitulée "Utilisation de @Query". Concentrez-vous sur la syntaxe de base et les deux manières de passer des paramètres à la requête : les paramètres nommés (avec @Param) et les paramètres positionnels (avec ?1).
Comme vous venez de le lire, il y a deux façons de lier les paramètres de votre méthode à la requête :
-
Paramètres Nommés (recommandé) :
- Syntaxe dans la requête :
:nomDuParametre - Syntaxe dans la méthode :
@Param("nomDuParametre") Type nom - Avantages : Très lisible et robuste. L'ordre des paramètres dans la méthode n'a pas d'importance. C'est la meilleure pratique.
- Syntaxe dans la requête :
-
Paramètres Positionnels :
- Syntaxe dans la requête :
?1,?2, etc. - Syntaxe dans la méthode : Les paramètres sont mappés par leur ordre d'apparition.
- Inconvénients : Moins lisible et fragile. Si vous changez l'ordre des paramètres de la méthode, votre requête ne fonctionnera plus. À n'utiliser que pour des requêtes très simples avec un ou deux paramètres.
- Syntaxe dans la requête :
2. La Sécurité avant tout : Prévention des injections SQL
Un point crucial que le "pattern" de passage de paramètres de Spring Data résout pour vous est la sécurité. En utilisant des paramètres (:nom ou ?1), Spring Data JPA utilise des PreparedStatement JDBC en coulisses.
Cela signifie que les valeurs que vous passez sont traitées comme des données et non comme une partie de la requête SQL. Toute tentative d'injection de code malveillant (par exemple, un utilisateur qui saisit '; DROP TABLE users; -- dans un champ de recherche) est automatiquement neutralisée. En tant que développeur professionnel, c'est un mécanisme que vous devez comprendre et utiliser systématiquement.
3. Cas pratique : Filtrer des produits
Assez de théorie, passons à la pratique. Nous allons voir comment créer une requête JPQL pour filtrer une liste de produits en fonction de plusieurs critères : leur catégorie et une fourchette de prix.
Spring Boot | Pagination et Filtre avec Spring Data JPA
La vidéo suivante montre un exemple concret et très propre de l'utilisation de @Query pour implémenter une fonctionnalité de filtre.
Regardez l'extrait de 06:51 à 08:58. L'auteur écrit une requête JPQL pour trouver des produits. Observez attentivement : La syntaxe de la requête select p from Product p where .... L'utilisation de p.category et p.price, qui sont des attributs de l'entité Product. La manière dont les paramètres nommés (:category, :minPrice) sont définis dans la requête et liés aux arguments de la méthode.
Analysons la requête et la méthode de la vidéo :
@Query("select p from Product p where p.category = :category and p.price between :minPrice and :maxPrice")
Page<Product> findByCategoryAndPrice(
@Param("category") String category,
@Param("minPrice") BigDecimal minPrice,
@Param("maxPrice") BigDecimal maxPrice,
Pageable pageable);
@Query(...): L'annotation qui contient notre requête personnalisée.select p from Product p: On sélectionne l'entitéProductcomplète (aliasséep).where p.category = :category: On filtre sur l'attributcategoryde l'entité.and p.price between :minPrice and :maxPrice: On ajoute un filtre sur le prix.@Param("category") String category: On lie le paramètre:categoryde la requête à l'argumentcategoryde la méthode.
C'est clair, explicite et beaucoup plus maintenable qu'une requête dérivée qui s'appellerait findByCategoryAndPriceBetween.
4. Aller plus loin : JPQL vs Requêtes Natives
Le JPQL est la norme et couvre 99% de vos besoins. Cependant, il peut arriver que vous ayez besoin d'utiliser une fonctionnalité spécifique à votre base de données (par exemple, une fonction particulière de PostgreSQL ou MySQL) qui n'est pas standardisée en JPQL.
Dans ce cas, @Query vous permet également d'écrire du SQL natif.
Tutoriel Spring Boot | Spring Data JPA | 2023
Cette section d'une autre vidéo illustre très bien la différence entre une requête JPQL et une requête native, et quand utiliser l'une ou l'autre.
Regardez de 01:07:26 à 01:14:49. L'auteur commence par réécrire une requête dérivée avec JPQL, puis il montre comment exécuter du SQL pur en ajoutant le drapeau nativeQuery = true à l'annotation @Query.
Pour résumer ce que vous avez vu :
-
JPQL (par défaut)
@Query("SELECT u FROM User u WHERE u.email LIKE :expression") List<User> findByEmailExpression(@Param("expression") String expression);- Avantages : Portable, orienté objet, plus sûr contre les erreurs de renommage de colonnes.
-
SQL Natif
@Query(value = "SELECT * FROM utilisateurs u WHERE u.email LIKE ?1", nativeQuery = true) List<User> findByEmailExpressionNative(String expression);- Avantages : Permet d'utiliser des fonctionnalités spécifiques au SGBD, peut être nécessaire pour des requêtes très complexes ou des optimisations fines.
- Inconvénients : Lie votre code à un SGBD spécifique, moins de sécurité au niveau de la compilation (vous manipulez des chaînes de caractères pour les noms de tables/colonnes).
En tant que développeur, il est souvent utile de savoir quel SQL est généré par votre ORM. Comprendre la traduction de JPQL en SQL est une compétence clé pour le débogage et l'optimisation.

Conclusion
Félicitations ! Vous avez ajouté un outil essentiel à votre arsenal de développeur Spring Boot. Vous savez maintenant comment dépasser les limites des requêtes dérivées pour écrire des requêtes personnalisées, complexes, lisibles et sécurisées.
Points clés à retenir :
- L'annotation
@Queryvous donne un contrôle total sur les requêtes associées à vos méthodes de repository. - JPQL est le langage standard pour interroger vos entités Java de manière orientée objet et portable.
- Utilisez systématiquement des paramètres nommés (
:param) avec@Parampour des requêtes claires, robustes et sécurisées contre les injections SQL. - En dernier recours, pour des besoins très spécifiques, vous pouvez utiliser du SQL natif en spécifiant
nativeQuery = true.
Dans les vidéos, vous avez aperçu le paramètre Pageable. C'est l'indice de notre prochaine leçon, où nous apprendrons à implémenter la pagination et le tri sur les résultats de nos requêtes. C'est une étape indispensable pour construire des APIs performantes qui ne surchargent ni le serveur ni le client avec des milliers de résultats.