Utiliser les requêtes de critères dans une application de données Spring

Utiliser des requêtes de critères dans une application de données Spring

1. introduction

Spring Data JPA offre de nombreuses façons de traiter les entités, notamment lesquery methods et lesJPQL queries personnalisés. Cependant, nous avons parfois besoin d'une approche plus programmatique: par exempleCriteria API ouQueryDSL.

Criteria API offers a programmatic way to create typed queries, ce qui nous aide à éviter les erreurs de syntaxe. Encore plus, lorsque nous l'utilisons avec Metamodel API, cela permet de vérifier à la compilation si nous avons utilisé les noms et les types de champs appropriés.

Cependant, il a ses inconvénients: nous devons écrire une logique prolixe surchargée de code passe-partout.

Dans ce didacticiel, nous verrons comment mettre en œuvre notre logique DAO personnalisée à l'aide de requêtes de critères et comment Spring aide à réduire le code standard.

2. Exemple d'application

Par souci de simplicité, dans les exemples, nous allons implémenter la même requête de plusieurs manières: trouver des livres par le nom de l'auteur et le titre contenant unString.

L'entitéBook pour cela ressemble à ceci:

@Entity
class Book {

    @Id
    Long id;
    String title;
    String author;

    // getters and setters

}

Parce que nous voulons garder les choses simples, nous n'utilisons pas l'API Metamodel dans ce tutoriel.

3. Classe@Repository

Comme nous le savons, dans le modèle de composant Springwe should place our data access logic in @Repository beans. Bien entendu, cette logique peut utiliser n'importe quelle implémentation, par exemple l'API Criteria.

Pour ce faire, nous n'avons besoin que d'une instanceEntityManager, que nous pouvons autowire:

@Repository
class BookDao {

    EntityManager em;

    // constructor

    List findBooksByAuthorNameAndTitle(String authorName, String title) {
        CriteriaBuilder cb = em.getCriteriaBuilder();
        CriteriaQuery cq = cb.createQuery(Book.class);

        Root book = cq.from(Book.class);
        Predicate authorNamePredicate = cb.equal(book.get("author"), authorName);
        Predicate titlePredicate = cb.like(book.get("title"), "%" + title + "%");
        cq.where(authorNamePredicate, titlePredicate);

        TypedQuery query = em.createQuery(cq);
        return query.getResultList();
    }

}

Le code ci-dessus suit un flux de travail standard de l'API Criteria:

  • Tout d'abord, nous obtenons une référenceCriteriaBuilder, que nous pouvons utiliser pour créer différentes parties de la requête

  • En utilisant lesCriteriaBuilder, nous créons unCriteriaQuery<Book>, qui décrit ce que nous voulons faire dans la requête. En outre, il déclare le type d'une ligne dans le résultat

  • AvecCriteriaQuery<Book>, nous déclarons le point de départ de la requête (entitéBook), et nous le stockons dans la variablebook pour une utilisation ultérieure

  • Ensuite, avecCriteriaBuilder, nous créons des prédicats contre notre entitéBook. Notez que ces prédicats n'ont pas encore d'effet

  • Nous appliquons les deux prédicats à notreCriteriaQuery.CriteriaQuery.where(Predicate…) combine ses arguments dans unand logique. C'est le moment où nous lions ces prédicats à la requête

  • Après cela, nous créons une instanceTypedQuery<Book> à partir de nosCriteriaQuery

  • Enfin, nous retournons toutes les entitésBook correspondantes

Notez que puisque nous avons marqué la classe DAO avec@Repository,Spring enables exception translation pour cette classe.

4. Extension du référentiel avec des méthodes personnalisées

Avoirautomatic custom queries est une fonctionnalité puissante de Spring Data. Cependant, nous avons parfois besoin d'une logique plus sophistiquée, que nous ne pouvons pas créer avec des méthodes de requête automatiques.

Nous pouvons implémenter ces requêtes dans des classes DAO distinctes (comme dans la section précédente).

De plus,if we want a @Repository interface to have a method with a custom implementation, we can use composable repositories.

L'interface personnalisée ressemble à ceci:

interface BookRepositoryCustom {
    List findBooksByAuthorNameAndTitle(String authorName, String title);
}

Et l'interface@Repository:

interface BookRepository extends JpaRepository, BookRepositoryCustom {}

De plus, nous devons modifier notre précédente classe DAO pour implémenterBookRepositoryCustom et la renommer enBookRepositoryImpl:

@Repository
class BookRepositoryImpl implements BookRepositoryCustom {

    EntityManager em;

    // constructor

    @Override
    List findBooksByAuthorNameAndTitle(String authorName, String title) {
        // implementation
    }

}

Lorsque nous déclaronsBookRepository comme dépendance, Spring trouveBookRepositoryImpl et l'utilise lorsque nous invoquons les méthodes personnalisées.

Disons que nous voulons sélectionner les prédicats à utiliser dans notre requête. Par exemple, lorsque nous ne voulons pas trouver les livres par auteur et par titre, nous avons seulement besoin que l'auteur corresponde.

Il existe plusieurs façons de le faire, par exemple, en appliquant un prédicat uniquement si l'argument passé n'est pasnull:

@Override
List findBooksByAuthorNameAndTitle(String authorName, String title) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery cq = cb.createQuery(Book.class);

    Root book = cq.from(Book.class);
    List predicates = new ArrayList<>();

    if (authorName != null) {
        predicates.add(cb.equal(book.get("author"), authorName));
    }
    if (title != null) {
        predicates.add(cb.like(book.get("title"), "%" + title + "%"));
    }
    cq.where(predicates.toArray(new Predicate[0]));

    return em.createQuery(cq).getResultList();
}

Cependant, cette approchemakes the code hard to maintain, surtout si nous avons de nombreux prédicats et que nous voulons les rendre optionnels.

Ce serait une solution pratique pour externaliser ces prédicats. Avec les spécifications JPA, nous pouvons faire exactement cela. et encore plus.

5. Utilisation des spécifications JPA

Spring Data a introduit l'interfaceorg.springframework.data.jpa.domain.Specification pour encapsuler un seul prédicat:

interface Specification {
    Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder cb);
}

Nous pouvons fournir des méthodes pour créer des instancesSpecification:

static Specification hasAuthor(String author) {
    return (book, cq, cb) -> cb.equal(book.get("author"), author);
}

static Specification titleContains(String title) {
    return (book, cq, cb) -> cb.like(book.get("title"), "%" + title + "%");
}

Pour les utiliser, nous avons besoin de notre référentiel pour étendreorg.springframework.data.jpa.repository.JpaSpecificationExecutor<T>:

interface BookRepository extends JpaRepository, JpaSpecificationExecutor {}

Cette interfacedeclares handy methods to work with specifications. Par exemple, nous pouvons maintenant trouver toutes les instancesBook avec l'auteur spécifié avec cette ligne unique:

bookRepository.findAll(hasAuthor(author));

Malheureusement, nous n’obtenons aucune méthode à laquelle nous pouvons transmettre plusieurs argumentsSpecification. Nous obtenons plutôt des méthodes utilitaires dans l'interfaceorg.springframework.data.jpa.domain.Specification.

Par exemple, combiner deux instancesSpecification avec des instances logiques et:

bookRepository.findAll(where(hasAuthor(author)).and(titleContains(title)));

Dans l'exemple ci-dessus,where() est une méthode statique de la classeSpecification.

De cette façon, nous pouvons rendre nos requêtes modulaires. De plus, nous n'avons pas eu à écrire le passe-partout de l'API Criteria: Spring nous l'a fourni.

Notez que cela ne signifie pas que nous n'aurons plus à écrire des critères standard; cette approche est uniquement capable de gérer le flux de travail que nous avons vu: sélectionner des entités qui satisfont à la ou aux conditions fournies.

Une requête peut avoir de nombreuses structures qu'elle ne prend pas en charge, par exemple le regroupement, le renvoi d'une classe différente dans laquelle nous sélectionnons ou des sous-requêtes.

6. Conclusion

Dans ce tutoriel, nous avons vu trois façons d'utiliser les requêtes de critères dans notre application Spring:

  • la création d'une classe DAO est la méthode la plus simple et la plus flexible

  • étendre une interface@Repository à une intégration transparente avec des requêtes automatiques

  • utiliser des prédicats dans les instancesSpecification pour rendre les cas simples plus propres et moins verbeux

Comme d'habitude, les exemples sont disponiblesover on GitHub.