web-dev-qa-db-fra.com

Spécifications JPA par exemple

Botte de printemps ici. J'essaie de comprendre JpaRepositories et Specifications lorsque je l'utilise dans le cadre de la mise en œuvre de requêtes complexes et que j'ai du mal à voir la "forêt à travers les arbres" sur plusieurs éléments.

Voici un exemple canonique de Specification:

public class PersonSpecification implements Specification<Person> {
    private Person filter;

    public PersonSpecification(Person filter) {
        super();
        this.filter = filter;
    }

    public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> cq,
            CriteriaBuilder cb) {
        Predicate p = cb.disjunction();

        if (filter.getName() != null) {
            p.getExpressions()
                    .add(cb.equal(root.get("name"), filter.getName()));
        }

        if (filter.getSurname() != null && filter.getAge() != null) {
            p.getExpressions().add(
                    cb.and(cb.equal(root.get("surname"), filter.getSurname()),
                            cb.equal(root.get("age"), filter.getAge())));
        }

        return p;
    }
}

Dans cette méthode toPredicate(...), que représentent les Root<Person> et CriteriaQuery? Plus important encore, il sonne comme vous devez créer un Specification impl pour chaque type de filtre que vous souhaitez appliquer, car chaque spécification est traduite en un et un seul prédicat ... donc Par exemple, si je voulais trouver toutes les personnes dont le nom de famille était "Smeeb" et dont l'âge était supérieur à 25 ans, il semblerait que j'aurais besoin d'écrire un LastnameMatchingSpecification<Person> ainsi qu'un AgeGreaterThanSpecification<Person>. Quelqu'un peut-il confirmer ou clarifier cela pour moi?!

4
smeeb

que représentent les Root<Person> et CriteriaQuery?

Root est la racine de votre requête. Quoi pour laquelle vous effectuez une requête. Dans Specification, vous pouvez l’utiliser pour réagir de manière dynamique à ce problème. Cela vous permettrait, par exemple, de créer un OlderThanSpecification pour gérer Cars avec un modelYear et Drivers avec un dateOfBirth en détectant le type et en utilisant la propriété appropriée.

Similiar CriteriaQuery est la requête complète que vous pouvez à nouveau utiliser pour l'inspecter et adapter le prédicat que vous construisez en fonction de celui-ci. 

si je voulais trouver toutes les personnes dont le nom de famille était "Smeeb" et dont l'âge était supérieur à 25 ans, il semblerait que j'aurais besoin d'écrire un LastnameMatchingSpecification<Person> ainsi qu'un AgeGreaterThanSpecification<Person>. Quelqu'un peut-il confirmer ou clarifier cela pour moi?!

Je pense que tu as tort. Les interfaces Spring Data acceptant Specification acceptent un seul Specification. Donc, si vous voulez trouver tous les Person avec un certain nom et un certain âge, vous créerez un Specification. Semblable à l'exemple que vous citez, qui combine également deux contraintes. 

Mais vous pouvez créer des Specifications séparés, puis en créer un autre combinant ceux-ci si vous souhaitez les utiliser séparément, mais également combinés.

3
Jens Schauder

Cela a été difficile pour moi aussi au début, mais maintenant je fais des requêtes dynamiques avec une seule spécification par table (quand la recherche avancée est nécessaire)

Pensez à ces objets comme:

  1. La racine est votre table.
  2. CriteriaQuery est votre requête, idéale pour appliquer des requêtes distinctes, des sous-requêtes, un ordre, etc.
  3. CriteriaBuilder est votre condition, bonne pour créer vos clauses where

-

Commencez toujours par une liste, puis condensez-les à la fin avec des conditions ET/OU en fonction de vos besoins.

public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
    List<Predicate> predicates = new ArrayList<>();

    if(filter.getName() != null) {
        predicates.add(cb.equal(root.get("name"), filter.getName());
    }
    if(filter.getSurname() != null) {
        predicates.add(cb.equal(root.get("surname"), filter.getSurname());
    }
    if(filter.getAge() != null) {
        predicates.add(cb.equal(root.get("age"), filter.getAge());
    }
    if(predicates.isEmpty()){
        predicates.add(cb.equal(root.get("id"), -1);
        /* 
         I like to add this because without it if no criteria is specified then 
         everything is returned. Because that's how queries work without where 
         clauses. However, if my user doesn't provide any criteria I want to 
         say no results found. 
        */
    }

    return query.where(cb.and(predicates.toArray(new Predicate[0])))
                .distinct(true).orderBy(cb.desc(root.get("name")).getRestriction();
}

Maintenant, mon utilisateur peut passer n'importe quelle combinaison de ces 3 champs ici et cette logique créerait dynamiquement la requête pour y inclure des conditions.

par exemple. name = Jean et nom de famille = Biche et age = 41ounom = Jean et age = 41.__ ou or__.nom = Jean etc.

Enfin, lors de la recherche de chaînes, je vous recommanderais d'utiliser cb.like et non cb.equal afin que votre recherche puisse faire une recherche partielle avec% est passé par l'utilisateur ou le système frontal. 

N'oubliez pas que cb.like n'est pas sensible à la casse par défaut, il doit être utilisé conjointement avec cb.lower ou cb.upper, tels que:

 predicates.add(cb.like(cb.lower(root.get("name"), filter.getName().toLowercase());

J'espère que cela t'aides !

1
jDub9