Spring Data JPA@Query

Aperçu

Spring Data fournit de nombreuses façons de définir une requête que nous pouvons exécuter. L’un d’eux est l’annotation @Query.

Dans ce tutoriel, nous allons montrer comment utiliser l’annotation @Query dans Spring Data JPA pour exécuter des requêtes JPQL et SQL natives.

Nous montrerons également comment créer une requête dynamique lorsque l’annotation @Query ne suffit pas.

Lectures complémentaires:

Méthodes de requête dérivées dans les référentiels JPA Spring Data

Explorez le mécanisme de dérivation de requête dans Spring Data JPA.
En savoir plus →

JPA de données Spring @Annotation de modification

Créez des requêtes DML et DDL dans JPA de données Spring en combinant les annotations @Query et @Modifying
En savoir plus →

Sélectionnez Query

Afin de définir SQL à exécuter pour une méthode de référentiel de données Spring, nous pouvons annoter la méthode avec l’annotation @Query — son attribut value contient le JPQL ou SQL à exécuter.

L’annotation @Query a priorité sur les requêtes nommées, qui sont annotées avec @NamedQuery ou définies dans un orm.fichier xml.

C’est une bonne approche de placer une définition de requête juste au-dessus de la méthode dans le référentiel plutôt que dans notre modèle de domaine en tant que requêtes nommées. Le référentiel est responsable de la persistance, c’est donc un meilleur endroit pour stocker ces définitions.

2.1. JPQL

Par défaut, la définition de la requête utilise JPQL.

Examinons une méthode de référentiel simple qui renvoie des entités utilisateur actives de la base de données:

@Query("SELECT u FROM User u WHERE u.status = 1")Collection<User> findAllActiveUsers();

2.2. Native

Nous pouvons également utiliser du SQL natif pour définir notre requête. Tout ce que nous avons à faire est de définir la valeur de l’attribut nativeQuery sur true et de définir la requête SQL native dans l’attribut value de l’annotation:

@Query( value = "SELECT * FROM USERS u WHERE u.status = 1", nativeQuery = true)Collection<User> findAllActiveUsersNative();

Définir l’ordre dans une requête

Nous pouvons passer un paramètre supplémentaire de type Sort à une déclaration de méthode de données Spring qui a l’annotation @Query. Il sera traduit dans la clause ORDER BY qui sera transmise à la base de données.

3.1. Tri pour les méthodes fournies et dérivées JPA

Pour les méthodes que nous sortons de la boîte telles que findAll (Sort) ou celles qui sont générées par l’analyse des signatures de méthode, nous ne pouvons utiliser que les propriétés d’objet pour définir notre tri:

userRepository.findAll(Sort.by(Sort.Direction.ASC, "name"));

Imaginez maintenant que nous voulons trier par la longueur d’une propriété de nom:

userRepository.findAll(Sort.by("LENGTH(name)"));

Lorsque nous exécutons le code ci-dessus, nous recevons une exception:

org.cadre printanier.données.cartographie.PropertyReferenceException: Aucune LONGUEUR de propriété (nom) trouvée pour le type User!

3.2. JPQL

Lorsque nous utilisons JPQL pour une définition de requête, Spring Data peut gérer le tri sans aucun problème — tout ce que nous avons à faire est d’ajouter un paramètre de méthode de type Sort:

@Query(value = "SELECT u FROM User u")List<User> findAllUsers(Sort sort);

Nous pouvons appeler cette méthode et passer un paramètre de tri, qui ordonnera le résultat par la propriété name de l’objet Utilisateur:

userRepository.findAllUsers(Sort.by("name"));

Et parce que nous avons utilisé l’annotation @Query, nous pouvons utiliser la même méthode pour obtenir la liste triée des utilisateurs par la longueur de leurs noms:

userRepository.findAllUsers(JpaSort.unsafe("LENGTH(name)"));

Il est crucial que nous utilisions JpaSort.unsafe() pour créer une instance d’objet de Tri.

Lorsque nous utilisons:

Sort.by("LENGTH(name)");

ensuite, nous recevrons exactement la même exception que celle que nous avons vue ci-dessus pour la méthode findAll().

Lorsque Spring Data découvre l’ordre de tri non sécurisé pour une méthode qui utilise l’annotation @Query, elle ajoute simplement la clause sort à la requête — elle ignore si la propriété à trier appartient au modèle de domaine.

3.3. Native

Lorsque l’annotation @Query utilise du SQL natif, il n’est pas possible de définir un Tri.

Si c’est le cas, nous recevrons une exception:

org.cadre printanier.données.jpa.référentiel.requête.InvalidJpaQueryMethodException : Impossible d’utiliser des requêtes natives avec un tri dynamique et / ou une pagination

Comme le dit l’exception, le tri n’est pas pris en charge pour les requêtes natives. Le message d’erreur nous indique que la pagination provoquera également une exception.

Cependant, il existe une solution de contournement qui active la pagination, et nous la couvrirons dans la section suivante.

Pagination

La pagination nous permet de renvoyer juste un sous-ensemble d’un résultat entier dans une Page. Ceci est utile, par exemple, lors de la navigation dans plusieurs pages de données sur une page Web.

Un autre avantage de la pagination est que la quantité de données envoyées du serveur au client est minimisée. En envoyant des données plus petites, nous pouvons généralement constater une amélioration des performances.

4.1. JPQL

L’utilisation de la pagination dans la définition de requête JPQL est simple:

@Query(value = "SELECT u FROM User u ORDER BY id")Page<User> findAllUsersWithPagination(Pageable pageable);

Nous pouvons passer un paramètre PageRequest pour obtenir une page de données.La pagination

est également prise en charge pour les requêtes natives, mais nécessite un peu de travail supplémentaire.

4.2. Native

Nous pouvons activer la pagination pour les requêtes natives en déclarant un attribut supplémentaire countQuery.

Cela définit le SQL à exécuter pour compter le nombre de lignes dans le résultat entier:

@Query( value = "SELECT * FROM Users ORDER BY id", countQuery = "SELECT count(*) FROM Users", nativeQuery = true)Page<User> findAllUsersWithPagination(Pageable pageable);

4.3. Versions JPA Spring Data Antérieures à 2.0.4

La solution ci-dessus pour les requêtes natives fonctionne correctement pour les versions JPA Spring Data 2.0.4 et ultérieures.

Avant cette version, lorsque nous essayons d’exécuter une telle requête, nous recevons la même exception que celle décrite dans la section précédente sur le tri.

Nous pouvons surmonter cela en ajoutant un paramètre supplémentaire pour la pagination dans notre requête:

@Query( value = "SELECT * FROM Users ORDER BY id \n-- #pageable\n", countQuery = "SELECT count(*) FROM Users", nativeQuery = true)Page<User> findAllUsersWithPagination(Pageable pageable);

Dans l’exemple ci-dessus, nous ajoutons « \n-#pageable\n » comme espace réservé pour le paramètre de pagination. Cela indique à Spring Data JPA comment analyser la requête et injecter le paramètre pageable. Cette solution fonctionne pour la base de données H2.

Nous avons expliqué comment créer des requêtes de sélection simples via JPQL et SQL natif. Ensuite, nous montrerons comment définir des paramètres supplémentaires.

Paramètres de requête indexés

Il existe deux façons possibles de transmettre des paramètres de méthode à notre requête : les paramètres indexés et les paramètres nommés.

Dans cette section, nous aborderons les paramètres indexés.

5.1. JPQL

Pour les paramètres indexés dans JPQL, les données Spring transmettent les paramètres de méthode à la requête dans le même ordre qu’ils apparaissent dans la déclaration de méthode:

@Query("SELECT u FROM User u WHERE u.status = ?1")User findUserByStatus(Integer status);@Query("SELECT u FROM User u WHERE u.status = ?1 and u.name = ?2")User findUserByStatusAndName(Integer status, String name);

Pour les requêtes ci-dessus, le paramètre de méthode d’état sera affecté au paramètre de requête avec l’index 1, et le paramètre de méthode de nom sera affecté au paramètre de requête avec l’index 2.

5.2. Les paramètres indexés natifs

pour les requêtes natives fonctionnent exactement de la même manière que pour JPQL:

@Query( value = "SELECT * FROM Users u WHERE u.status = ?1", nativeQuery = true)User findUserByStatusNative(Integer status);

Dans la section suivante, nous montrerons une approche différente: passer des paramètres via le nom.

Paramètres nommés

Nous pouvons également transmettre des paramètres de méthode à la requête en utilisant des paramètres nommés. Nous les définissons à l’aide de l’annotation @Param dans notre déclaration de méthode de référentiel.

Chaque paramètre annoté avec @Param doit avoir une chaîne de valeur correspondant au nom de paramètre de requête JPQL ou SQL correspondant. Une requête avec des paramètres nommés est plus facile à lire et moins sujette aux erreurs au cas où la requête aurait besoin d’être refactorisée.

6.1. JPQL

Comme mentionné ci-dessus, nous utilisons l’annotation @Param dans la déclaration de méthode pour faire correspondre les paramètres définis par name dans JPQL avec les paramètres de la déclaration de méthode:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")User findUserByStatusAndNameNamedParams( @Param("status") Integer status, @Param("name") String name);

Notez que dans l’exemple ci-dessus, nous avons défini nos paramètres de requête SQL et de méthode pour avoir les mêmes noms, mais ce n’est pas nécessaire tant que les chaînes de valeurs sont les mêmes:

@Query("SELECT u FROM User u WHERE u.status = :status and u.name = :name")User findUserByUserStatusAndUserName(@Param("status") Integer userStatus, @Param("name") String userName);

6.2. Native

Pour la définition de requête native, il n’y a aucune différence dans la façon dont nous transmettons un paramètre via le nom à la requête par rapport à JPQL — nous utilisons l’annotation @Param:

@Query(value = "SELECT * FROM Users u WHERE u.status = :status and u.name = :name", nativeQuery = true)User findUserByStatusAndNameNamedParamsNative( @Param("status") Integer status, @Param("name") String name);

7. Paramètre de collection

Considérons le cas où la clause where de notre requête JPQL ou SQL contient le mot clé IN (ou NOT IN):

SELECT u FROM User u WHERE u.name IN :names

Dans ce cas, nous pouvons définir une méthode de requête qui prend Collection comme paramètre:

@Query(value = "SELECT u FROM User u WHERE u.name IN :names")List<User> findUserByNameList(@Param("names") Collection<String> names);

Comme le paramètre est une collection, il peut être utilisé avec List, HashSet, etc.

Ensuite, nous montrerons comment modifier les données avec l’annotation @Modifying.

Mettre à jour les requêtes Avec @Modifying

Nous pouvons utiliser l’annotation @Query pour modifier l’état de la base de données en ajoutant également l’annotation @Modifying à la méthode repository.

8.1. JPQL

La méthode de référentiel qui modifie les données présente deux différences par rapport à la requête select — elle a l’annotation @Modifying et, bien sûr, la requête JPQL utilise update au lieu de select:

@Modifying@Query("update User u set u.status = :status where u.name = :name")int updateUserSetStatusForName(@Param("status") Integer status, @Param("name") String name);

La valeur de retour définit le nombre de lignes mises à jour par l’exécution de la requête. Les paramètres indexés et nommés peuvent être utilisés dans les requêtes de mise à jour.

8.2. Native

Nous pouvons également modifier l’état de la base de données avec une requête native. Nous avons juste besoin d’ajouter l’annotation @Modifying:

@Modifying@Query(value = "update Users u set u.status = ? where u.name = ?", nativeQuery = true)int updateUserSetStatusForNameNative(Integer status, String name);

8.3. Insertions

Pour effectuer une opération d’insertion, nous devons à la fois appliquer @Modifying et utiliser une requête native car INSERT ne fait pas partie de l’interface JPA:

@Modifying@Query( value = "insert into Users (name, age, email, status) values (:name, :age, :email, :status)", nativeQuery = true)void insertUser(@Param("name") String name, @Param("age") Integer age, @Param("status") Integer status, @Param("email") String email);

Requête dynamique

Souvent, nous rencontrons le besoin de créer des instructions SQL basées sur des conditions ou des ensembles de données dont les valeurs ne sont connues qu’à l’exécution. Et dans ces cas, nous ne pouvons pas simplement utiliser une requête statique.

9.1. Exemple de requête dynamique

Par exemple, imaginons une situation où nous devons sélectionner tous les utilisateurs dont le courrier électronique est COMME un parmi un ensemble défini à l’exécution – email1, email2, …, emailn:

SELECT u FROM User u WHERE u.email LIKE '%email1%' or u.email LIKE '%email2%' ... or u.email LIKE '%emailn%'

Comme l’ensemble est construit dynamiquement, nous ne pouvons pas savoir au moment de la compilation combien de clauses LIKE ajouter.

Dans ce cas, nous ne pouvons pas simplement utiliser l’annotation @Query car nous ne pouvons pas fournir d’instruction SQL statique.

Au lieu de cela, en implémentant un référentiel composite personnalisé, nous pouvons étendre la fonctionnalité JpaRepository de base et fournir notre propre logique pour créer une requête dynamique. Jetons un coup d’œil à la façon de le faire.

9.2. Référentiels personnalisés et API de critères JPA

Heureusement pour nous, Spring fournit un moyen d’étendre le référentiel de base grâce à l’utilisation d’interfaces de fragments personnalisées. Nous pouvons ensuite les lier ensemble pour créer un référentiel composite.

Nous allons commencer par créer une interface de fragment personnalisée:

public interface UserRepositoryCustom { List<User> findUserByEmails(Set<String> emails);}

Et puis nous allons l’implémenter:

public class UserRepositoryCustomImpl implements UserRepositoryCustom { @PersistenceContext private EntityManager entityManager; @Override public List<User> findUserByEmails(Set<String> emails) { CriteriaBuilder cb = entityManager.getCriteriaBuilder(); CriteriaQuery<User> query = cb.createQuery(User.class); Root<User> user = query.from(User.class); Path<String> emailPath = user.get("email"); List<Predicate> predicates = new ArrayList<>(); for (String email : emails) { predicates.add(cb.like(emailPath, email)); } query.select(user) .where(cb.or(predicates.toArray(new Predicate))); return entityManager.createQuery(query) .getResultList(); }}

Comme indiqué ci-dessus, nous avons tiré parti de l’API JPA Criteria pour créer notre requête dynamique.

De plus, nous devons nous assurer d’inclure le postfix Impl dans le nom de la classe. Spring recherchera l’implémentation UserRepositoryCustom en tant que UserRepositoryCustomImpl. Puisque les fragments ne sont pas des dépôts en eux-mêmes, Spring s’appuie sur ce mécanisme pour trouver l’implémentation du fragment.

9.3. Extension du référentiel existant

Notez que toutes les méthodes de requête de la section 2 à la section 7 se trouvent dans le répertoire utilisateur.

Donc, maintenant, nous allons intégrer notre fragment en étendant la nouvelle interface dans le répertoire utilisateur:

public interface UserRepository extends JpaRepository<User, Integer>, UserRepositoryCustom { // query methods from section 2 - section 7}

9.4. En utilisant le référentiel

Et enfin, nous pouvons appeler notre méthode de requête dynamique:

Set<String> emails = new HashSet<>();// filling the set with any number of itemsuserRepository.findUserByEmails(emails);

Nous avons réussi à créer un référentiel composite et appelé notre méthode personnalisée.

Conclusion

Dans cet article, nous avons couvert plusieurs façons de définir des requêtes dans les méthodes de référentiel JPA Spring Data à l’aide de l’annotation @Query.

Nous avons également appris à implémenter un référentiel personnalisé et à créer une requête dynamique.

Comme toujours, les exemples de code complets utilisés dans cet article sont disponibles sur GitHub.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.