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?
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();
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.
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é.
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
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);