web-dev-qa-db-fra.com

API de critères JPA avec plusieurs paramètres

J'ai besoin de créer une méthode de recherche qui utilise l'API JPA Criteria avec plusieurs paramètres. Maintenant, le problème est que tous les paramètres ne sont pas requis. Certains pourraient donc être nuls et ne devraient pas être inclus dans la requête. J'ai essayé cela avec CriteriaBuilder mais je ne voyais pas comment le faire fonctionner.

Avec l'API Hibernate Criteria, c'est assez facile. Créez simplement les critères, puis ajoutez des restrictions.

Criteria criteria = session.createCriteria(someClass.class);
if(someClass.getName() != null) {
   criteria.add(Restrictions.like("name", someClass.getName());
}

Comment pourrais-je obtenir le même résultat avec l'API Criteria de JPA?

31
mokuril

Le concept est de construire un tableau de javax.persistence.Predicate qui ne contient que les prédicats que nous voulons utiliser:

Exemple d'entité à interroger:

@Entity
public class A {
    @Id private Long id;    
    String someAttribute;
    String someOtherAttribute;
    ...
}

Interrogez-vous:

    //some parameters to your method
    String param1 = "1";
    String paramNull = null;

    CriteriaBuilder qb = em.getCriteriaBuilder();
    CriteriaQuery cq = qb.createQuery();
    Root<A> customer = cq.from(A.class);

    //Constructing list of parameters
    List<Predicate> predicates = new ArrayList<Predicate>();

    //Adding predicates in case of parameter not being null
    if (param1 != null) {
        predicates.add(
                qb.equal(customer.get("someAttribute"), param1));
    }
    if (paramNull != null) {
        predicates.add(
                qb.equal(customer.get("someOtherAttribute"), paramNull));
    }
    //query itself
    cq.select(customer)
            .where(predicates.toArray(new Predicate[]{}));
    //execute query and do something with result
    em.createQuery(cq).getResultList();
78
Mikko Maunu

Jetez un œil à ce site API des critères JPA . Il existe de nombreux exemples.

Mise à jour: fournir un exemple concret

Cherchons des comptes avec un solde inférieur à une valeur spécifique:

SELECT a FROM Account a WHERE a.balance < :value

Créez d'abord un générateur de critères

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Account> accountQuery = builder.createQuery(Account.class);
Root<Account> accountRoot = accountQuery.from(Account.class);
ParameterExpression<Double> value = builder.parameter(Double.class);
accountQuery.select(accountRoot).where(builder.lt(accountRoot.get("balance"), value));

Pour obtenir le résultat, définissez le ou les paramètres et exécutez la requête:

TypedQuery<Account> query = entityManager.createQuery(accountQuery);
query.setParameter(value, 1234.5);
List<Account> results = query.getResultList();

BTW: L'entityManager est injecté quelque part dans un EJB/Service/DAO.

6
Sal

La réponse de Mikko a fonctionné à merveille. Le seul changement que je devais faire était de remplacer:

cq.select(customer).where(predicates.toArray(new Predicate[]{}));

avec:

Predicate [] predicatesarr = predicates.toArray(new Predicate[predicates.size()]); 
cq.select(customer).where(predicatesarr);

Quelque part, la conversion de la liste au tableau dans l'original n'a pas fonctionné.

1
Vandana Chadha

Tout d'abord, la réponse de Mikko m'a amené à ma réponse. Votez pour cela.

Mon scénario était que je voulais une relation parent/enfant et je voulais trouver une correspondance avec ~ n'importe quel ~ enfant.

L'employé a plusieurs JobTitle (s).

Je voulais trouver un employé (où il y a de nombreux titres d'emploi), mais le trouver sur ~ n'importe lequel des titres de poste que j'envoie.

SQL ressemblerait à:

Sélectionnez * dans dbo.Employee et rejoignez dbo.JobTitle jt sur e.EmployeeKey = jt.EmployeeKey OERE (jt.JobTitleName = 'programmer' OR jt.JobTitleName = 'badcop')

J'ai ajouté le sexe et la date de naissance pour compléter l'exemple (et donner plus de critères "facultatifs")

Mon code JPA

import org.Apache.commons.lang3.StringUtils;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import Java.util.ArrayList;
import Java.util.List;

public class MyEmployeeSpecification implements Specification<MyEmployee> {

    private MyEmployee filter;

    public MyEmployeeSpecification(MyEmployee filter) {
        super();
        this.filter = filter;
    }

    public Predicate toPredicate(Root<MyEmployee> root, CriteriaQuery<?> cq,
                                 CriteriaBuilder cb) {

        Predicate returnPred = cb.disjunction();

        List<Predicate> patientLevelPredicates = new ArrayList<Predicate>();

        if (filter.getBirthDate() != null) {
            patientLevelPredicates.add(
                    cb.equal(root.get("birthDate"), filter.getBirthDate()));
        }

        if (filter.getBirthDate() != null) {
            patientLevelPredicates.add(
                    cb.equal(root.get("gender"), filter.getGender()));
        }

        if (null != filter.getJobTitles() && filter.getJobTitles().size() > 0) {

            List<Predicate> jobTitleLevelPredicates = new ArrayList<Predicate>();

            Join<JobTitle, JobTitle> hnJoin = root.join("jobtitles");

            for (JobTitle hnw : filter.getJobTitles()) {
                if (null != hnw) {
                    if (StringUtils.isNotBlank(hnw.getJobTitleName())) {
                        jobTitleLevelPredicates.add(cb.equal(hnJoin.get("getJobTitleName"), hnw.getFamily()));
                    }
                }
            }

            patientLevelPredicates.add(cb.or(jobTitleLevelPredicates.toArray(new Predicate[]{})));
        }

        returnPred = cb.and(patientLevelPredicates.toArray(new Predicate[]{}));

        return returnPred;
    }
}

Mais j'ai compris le mien à cause de predicates.toArray (nouveau Predicate [] {}), alias, l'astuce varargs. (Merci Mikko)

Je fais aussi la méthode "implémente Specifiction".

Autres liens utiles:

Spécifications JPA par exemple

critères de conjonction JPA CriteriaBuilder en critères de disjonction

0
granadaCoder

Une solution simple pour Spring, utilisant des expressions lambda:

Specification<User> specification = (root, query, builder) -> {
    List<Predicate> predicates = new ArrayList<>();
    if (criteria.getName() != null) {
        // like
        predicates.add(builder.like(root.get("name"), "%" + criteria.getName() + "%"));
    }
    if (criteria.getParentId() != 0) {
        // equal
        predicates.add(builder.equal(root.get("parent"), criteria.getParentId()));
    }

    // AND all predicates
    return builder.and(predicates.toArray(new Predicate[0]));
};

repository.findAll(specification);
0
Hamid Mohayeji