Spring Data JPA @Query

Données de printemps JPA @Query

1. Vue d'ensemble

Spring Data offre de nombreuses façons de définir une requête que nous pouvons exécuter. L'une d'elles est l'annotation@Query.

Dans ce didacticiel, nous allons démontrerhow to use the @Query annotation in Spring Data JPA to execute both JPQL and native SQL queries.

En outre, nous montrerons 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 de Spring Data

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

Read more

Spring Data JPA @Modifying Annotation

Créez des requêtes DML et DDL dans Spring Data JPA en combinant les annotations @Query et @Modifying

Read more

2. Sélectionner une requête

Afin de définir SQL à exécuter pour une méthode de référentiel Spring Data, nous pouvonsannotate the method with the @Query annotation  — its value attribute contains the JPQL or SQL to execute.

L'annotation@Query est prioritaire sur les requêtes nommées, qui sont annotées avec@NamedQuery ou définies dans un fichierorm.xml.

Il est judicieux 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 les entitésUser actives de la base de données:

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

2.2. Originaire de

Nous pouvons également utiliser le SQL natif pour définir notre requête. Tout ce que nous avons à faire est de définir la valeur de l'attributnativeQuery surtrue et de définir la requête SQL native dans l'attributvalue de l'annotation:

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

3. Définir l'ordre dans une requête

Nous pouvons passer un paramètre supplémentaire de typeSort à une déclaration de méthode Spring Data qui a l'annotation@Query. Il sera traduit dans la clauseORDER BY qui sera transmise à la base de données.

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

Pour les méthodes, nous obtenons des méthodes telles quefindAll(Sort) ou celles générées par l'analyse des signatures de méthode,we can only use object properties to define our sort:

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

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

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

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

org.springframework.data.mapping.PropertyReferenceException: No property lENGTH(name) found for type User!

3.2. JPQL

When we use JPQL for a query definition, then Spring Data can handle sorting without any problem - tout ce que nous avons à faire est d'ajouter un paramètre de méthode de typeSort:

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

Nous pouvons appeler cette méthode et passer un paramètreSort, qui ordonnera le résultat par la propriéténame de l'objetUser:

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

Et parce que nous avons utilisé l'annotation@Query,we can use the same method to get the sorted list of Users by the length of their names:

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

Il est essentiel d’utiliserJpaSort.unsafe() pour créer une instance d’objetSort.

Quand on utilise:

new Sort("LENGTH(name)");

alors nous recevrons exactement la même exception que celle que nous avons vue ci-dessus pour la méthodefindAll().

Lorsque Spring Data découvre l'ordreSort dangereux pour une méthode qui utilise l'annotation@Query, il ajoute simplement la clause de tri à la requête - il ignore si la propriété à trier appartient au modèle de domaine .

3.3. Originaire de

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

Si nous le faisons, nous recevrons une exception:

org.springframework.data.jpa.repository.query.InvalidJpaQueryMethodException: Cannot use native queries with dynamic sorting and/or 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 permet la pagination, et nous en parlerons dans la section suivante.

4. Pagination

La pagination nous permet de renvoyer juste un sous-ensemble d'un résultat entier dans unPage. Ceci est utile, par exemple, lorsque vous parcourez 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

Utiliser la pagination dans la définition de requête JPQL est simple:

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

Nous pouvons passer un paramètrePageRequest 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. Originaire de

Nous pouvonsenable pagination for native queries by declaring an additional attribute countQuery - cela définit le SQL à exécuter pour compter le nombre de lignes dans l'ensemble du résultat:

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

4.3. Versions de Spring Data JPA antérieures à 2.0.4

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

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

Nous pouvons résoudre ce problème 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 findAllUsersWithPagination(Pageable pageable);

”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éesH2.

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.

5. Paramètres de requête indexés

Il existe deux manières possibles de passer des paramètres de méthode à notre requête. Dans cette section, nous aborderons les paramètres indexés.

5.1. JPQL

Pour les paramètres indexés dans JPQL, Spring Data serapass method parameters to the query in the same order they appear in the method declaration:

@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éthodestatus sera affecté au paramètre de requête avec l'index1, et le paramètre de méthodename sera affecté au paramètre de requête avec l'index2 .

5.2. Originaire de

Les paramètres indexés 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 allons montrer une approche différente: passer des paramètres via le nom.

6. Paramètres nommés

Nous pouvons aussipass method parameters to the query using named parameters. Nous les définissons en utilisant 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 du 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 si elle doit ê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 nom 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 et de méthode SQL pour avoir les mêmes noms, mais ce n'est pas obligatoire, 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. Originaire de

Pour la définition de requête native, il n'y a aucune différence dans la façon dont nous passons 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

Prenons le cas où la clausewhere de notre requête JPQL ou SQL contient le mot cléIN (ouNOT 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 prendCollection  comme paramètre:

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

Comme le paramètre est unCollection, il peut être utilisé avecList, HashSet, etc.

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

8. Mettre à jour les requêtes avec@Modifying

Nous pouvonsuse the @Query annotation to modify the state of the database by also adding the @Modifying annotation à la méthode du référentiel.

8.1. JPQL

La méthode de référentiel qui modifie les données a deux différences par rapport à la requêteselect - elle a l'annotation@Modifying et, bien sûr, la requête JPQL utiliseupdate au lieu deselect:

@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. Originaire de

Nous pouvons également modifier l'état de la base de données avec une requête native - il suffit 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. Inserts

Pour effectuer une opération d'insertion, nous devons à la fois appliquer@Modifying et utiliser une requête native depuisINSERT is not a part of the JPA interface:

@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);

9. 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'au moment de 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 l'email estLIKE one dans 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 à la compilation combien de clausesLIKE ajouter.

Dans ce cas,we can’t just use the @Query annotation since we can’t provide a static SQL statement.

Au lieu de cela, en implémentant un référentiel composite personnalisé, nous pouvons étendre la fonctionnalité de baseJpaRepository et fournir notre propre logique pour créer une requête dynamique. Voyons comment procéder.

9.2. Référentiels personnalisés et API JPA Criteria

Luckily for us, Spring provides a way for extending the base repository through the use of custom fragment interfaces. Nous pouvons alors les lier ensemble pour créer uncomposite repository.

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

public interface UserRepositoryCustom {
    List findUserByEmails(Set emails);
}

Et puis, nous allons le mettre en œuvre:

public class UserRepositoryCustomImpl implements UserRepositoryCustom {

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List findUserByEmails(Set emails) {
        CriteriaBuilder cb = entityManager.getCriteriaBuilder();
        CriteriaQuery query = cb.createQuery(User.class);
        Root user = query.from(User.class);

        Path emailPath = user.get("email");

        List predicates = new ArrayList<>();
        for (String email : emails) {
            predicates.add(cb.like(emailPath, email));
        }
        query.select(user)
            .where(cb.or(predicates.toArray(new Predicate[predicates.size()])));

        return entityManager.createQuery(query)
            .getResultList();
    }
}

Comme indiqué ci-dessus, nous avons exploité lesJPA Criteria API pour créer notre requête dynamique.

Also, we need to make sure to include the Impl postfix in the class name. Spring recherchera l'implémentation deUserRepositoryCustom en tant queUserRepositoryCustomImpl. Comme les fragments ne sont pas des référentiels en soi, Spring s'appuie sur ce mécanisme pour trouver la mise en œuvre de 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 sont dans lesUserRepository. Alors maintenant, nous allons intégrer notre fragment en étendant la nouvelle interface dans lesUserRepository:

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

9.4. Utiliser le référentiel

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

Set emails = new HashSet<>();
// filling the set with any number of items

userRepository.findUserByEmails(emails);

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

10. Conclusion

Dans cet article, nous avons couvert plusieurs façons de définir des requêtes dans les méthodes de référentiel Spring Data JPA à 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 ce didacticiel sont disponiblesover on Github.