Criteria查询

JPQL虽然简单快捷,但面对复杂动态查询时可能不太方便,毕竟如果查询条件都是动态的,难道我们还要自己用复杂的代码逻辑拼接JPQL字符串吗?实际上,JPA中还支持Criteria查询。它和JPQL不同,Criteria查询是完全使用Java代码构造的,Criteria查询用于构建复杂的动态查询比JPQL更合适。

使用JPA的CriteriaAPI

下面例子代码我们使用CriteriaAPI实现了一个动态查询,当queryStudentDto中的查询条件存在时就将其拼接到动态查询下。

package com.gacfox.netstore.ejb;

import com.gacfox.netstore.api.DemoService;
import com.gacfox.netstore.api.model.QueryStudentDto;
import com.gacfox.netstore.ejb.entity.Student;
import org.jboss.logging.Logger;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.List;

@Stateless
public class DemoServiceImpl implements DemoService {
    @PersistenceContext(unitName = "jpa-netstorer-pu")
    private EntityManager entityManager;

    private static final Logger logger = Logger.getLogger(HelloServiceImpl.class);

    @Override
    public void demo(QueryStudentDto queryStudentDto) {
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        CriteriaQuery<Student> query = criteriaBuilder.createQuery(Student.class);
        Root<Student> root = query.from(Student.class);

        // 构建动态查询条件
        Predicate predicate = criteriaBuilder.conjunction();
        if (queryStudentDto.getName() != null) {
            predicate.getExpressions().add(criteriaBuilder.like(root.get("name"), "%" + queryStudentDto.getName() + "%"));
        }
        if (queryStudentDto.getAge() != null) {
            predicate.getExpressions().add(criteriaBuilder.equal(root.get("age"), queryStudentDto.getAge()));
        }
        query.where(predicate);

        // 执行查询
        List<Student> studentList = entityManager.createQuery(query).getResultList();

        logger.infov("Result: {0}" + studentList.size());
    }
}

代码中,CriteriaBuilder是JPA中用于构建动态查询的接口,CriteriaQuery是用于构建查询的类,Root是根实体,用于从数据库中选择数据。Predicate是用来查询的条件对象,criteriaBuilder.conjunction()方法返回一个AND连接的空条件(相当于where 1=1),它其实可以理解为就是返回一个AND连接的基础,我们可以将它看作是查询的起始点,然后通过添加其他条件来扩展这个查询。动态构造查询后,query.where(predicate)将所有条件组合在一起,作为查询的WHERE子句。

Criteria查询乍一看可能确实比较复杂,但实际上它和SQL/JPQL查询的构建逻辑是类似的,都是构造声明式的结构化查询,API中并没有什么复杂的算法。

Predicate组合

Predicate的用法十分灵活,调用criteriaBuilder.and()criteriaBuilder.or()我们可以组合出非常复杂的动态查询逻辑。

下面两种写法都是拼接若干AND条件,第一种写法可以理解为先给出一个where 1=1条件,然后再其后拼接其它条件;第二种写法可以理解为将conditon1condition2合并为AND条件,它们是等效的,区别主要在于代码的风格和表达方式。

Predicate finalCondition = criteriaBuilder.conjunction();
finalCondition.getExpressions().add(conditon1);
finalCondition.getExpressions().add(conditon2);
query.where(finalCondition);
Predicate finalCondition = criteriaBuilder.and(conditon1, conditon2);
query.where(finalCondition);

对于第二种写法,动态查询的条件可能是不定长的,criteriaBuilder.and()方法的参数其实也是不定长的,实际开发中我们也可以传入一个数组类型的参数。

List<Predicate> predicates = new ArrayList<>();
predicates.add(conditon1);
predicates.add(conditon2);
Predicate finalCondition = criteriaBuilder.and(predicates.toArray(new Predicate[0]));
query.where(finalCondition);

但我们要注意它的参数不能是空数组,如果用户一个条件都没有传入,传入空数组会报错,因此我们可能还是需要传入一个criteriaBuilder.conjunction()条件作为起始条件查询全部数据,或者直接返回空拒绝这种不带条件的查询。

另外对于OR条件,我们可以使用criteriaBuilder.or()方法生成,它的参数也是不定长的,可以传入多个条件或传入数组。与AND查询类似,如果用户没有查询条件,我们可能还需要拼接一个criteriaBuilder.disjunction()空条件。

下面例子组合拼接了AND和OR查询条件,嵌套拼接这些Predicate便可以组合出复杂的查询逻辑。

Predicate andCondition = criteriaBuilder.and(conditon1, conditon2);
Predicate orCondition = criteriaBuilder.or(conditon3, conditon4);
Predicate finalCondition = criteriaBuilder.and(andCondition, orCondition);
query.where(finalCondition);
作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap