39

我需要创建一个使用具有多个参数的 JPA Criteria API 的搜索方法。现在的问题是并非每个参数都是必需的。所以有些可能是空的,它们不应该包含在查询中。我已经用 CriteriaBuilder 试过这个,但我看不出如何让它工作。

使用 Hibernate Criteria API,这相当容易。只需创建条件,然后添加限制。

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

我如何使用 JPA 的 Criteria API 实现相同的目标?

4

5 回答 5

95

概念是构造javax.persistence.Predicate数组,其中仅包含我们要使用的谓词:

要查询的示例实体:

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

查询本身:

    //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();
于 2012-08-30T15:04:46.103 回答
7

看看这个站点JPA Criteria API。有很多例子。

更新:提供一个具体的例子

让我们搜索余额低于特定值的 Accounts:

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

首先创建一个 Criteria Builder

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

要获取结果集参数并运行查询:

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

顺便说一句:entityManager 被注入到 EJB/Service/DAO 的某个地方。

于 2012-08-30T14:50:20.800 回答
5

一个简单的 Spring 解决方案,使用 lambda 表达式:

Specification<User> specification = (root, query, builder) -> {
    List<Predicate> predicates = new ArrayList<>();

    // like
    predicates.add(builder.like(root.get("name"), "%test%"));

    // equal
    predicates.add(builder.equal(root.get("parent_id"), 99L);


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

repository.findAll(specification);
于 2019-07-20T20:38:23.423 回答
2

Mikko 的回答效果很好。我需要做的唯一改变就是更换:

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

和:

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

原版中从列表到数组的转换在某处不起作用。

于 2016-10-10T05:16:46.747 回答
0

首先,Mikko 的回答让我得到了答案。为此点赞。

我的场景是我想要父母/孩子的关系,我想在 ~any~ 孩子身上找到一个匹配。

员工有多个 JobTitle(s)。

我想找一个员工(那里有很多职位),但是在我发送的任何职位上都可以找到它。

SQL 看起来像:

Select * from dbo.Employee e join dbo.JobTitle jt on e.EmployeeKey = jt.EmployeeKey WHERE (jt.JobTitleName = 'programmer' OR jt.JobTitleName = 'badcop')

我输入了性别和出生日期以完成示例(并提供更多“可选”)标准)

我的 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;
    }
}

但我发现我的原因是 predicates.toArray(new Predicate[]{}) ,也就是 varargs 技巧。(感谢米高)

我也在做“实现规范”方法。

其他有用的链接:

JPA 规范示例

JPA CriteriaBuilder 将合取条件变成析取条件

于 2019-04-05T14:48:38.630 回答