zoukankan      html  css  js  c++  java
  • Spring Data JPA 的 Specifications动态查询

    0.0 概述

    JPA 2 引入了criteria API来构建查询语句中的where条件。这些查询条件用CriteriaBuilder来构建,构建的每个条件都是Predicate对象。

    Specification类实现了该API。通过继承JpaSpecificationExecutor接口,就可以使用Specification进行查询。

    1.0 适用范围

    • 动态构建查询语句
    • 支持所有的查询条件
    • 支持子查询、连接查询、排序、分页
    • 不支持自定义模型,无法做到将查询的结果封装为自定义的model,或类似List<String>的单列结果。只能返回dao定义的entity。
      即,只支持select * from ...

    1.1 JpaSpecificationExecutor接口方法介绍

    public interface JpaSpecificationExecutor<T> {
      
        // 查询单个对象
        Optional<T> findOne(@Nullable Specification<T> spec);
    
        // 查询列表
        List<T> findAll(@Nullable Specification<T> spec);
    
        // 查询全部,分页;可以使用PageRequest#of(int, int, Sort)方法创建pageable对象实现分页+排序
        Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);
    
        // 查询全部,排序
        List<T> findAll(@Nullable Specification<T> spec, Sort sort);
    
        // 统计查询
        long count(@Nullable Specification<T> spec);
      
    }
    

    1.2 Specification

    Specification类是一个用于构建动态查询条件的抽象类。

    public interface Specification<T> {
        Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder);
    }
    

    1.2.1 toPredicate方法介绍

    Specification类通过实现抽象方法Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder)来构建动态查询条件,查询条件最终会生成一个查询条件对象(Predicate对象)

    • Root类:用于获取需要查询的列
      • 如:root.get("lastName")
    • CriteriaQuery类:用于自定义查询方式
      • 如:query.distinct(true).select(...).where(...).groupBy(...).having(...).getRestriction()。最后通过getRestriction()方法,可以获得一个Predicate对象
      • 详见
    • CriteriaBuilder类:构造查询条件(即,用于构建Predicate对象),内部封装了很多的查询条件
      • 如:criteriaBuilder.and(...)criteriaBuilder.equal(...)criteriaBuilder.greaterThan)criteriaBuilder.greaterThanOrEqualTo)等,返回的结果是一个Predicate对象
      • 详见

    1.2.2 继承关系

    Root类、CriteriaQuery类、CriteriaBuilder类都在javax.persistence.criteria包下,其继承关系如下:

    1.3 示例

    1.3.1 简单示例

    (1) 编写查询条件
    @Test
    public void testSimpleCbSpecification() {
        Specification<RetPrdInfo> specification = (root, query, cb) -> {
            // 等于
            Predicate predicate1 = cb.equal(root.get("crToolRunMode"), "CLN");
            // 不等于
            Predicate predicate2 = cb.notEqual(root.get("prdSeqId"), "L000001");
            // 大于
            Predicate predicate3 = cb.greaterThan(root.get("prdQty"), 1);
            // 大于等于
            Predicate predicate4 = cb.greaterThanOrEqualTo(root.get("prdStdQty"), 1);
            // 小于
            Predicate predicate5 = cb.lessThan(root.get("prdQty"), 2);
            // 小于等于
            Predicate predicate6 = cb.lessThanOrEqualTo(root.get("prdStdQty"), 2);
            // between
            Timestamp startTime = Timestamp.valueOf(LocalDateTime.of(2021, 1, 1, 8, 0, 0));
            Timestamp endTime = Timestamp.valueOf(LocalDateTime.of(2022, 1, 1, 8, 0, 0));
            Predicate predicate7 = cb.between(root.get("evtTimestamp"), startTime, endTime);
            // 非空
            Predicate predicate8 = cb.isNotNull(root.get("prty"));
            // 为空
            Predicate predicate9 = cb.isNull(root.get("costCode"));
            // 模糊查询
            Predicate predicate10 = cb.like(root.get("tempPrdSeqId"), "L%");
            // in
            Predicate predicate11 = root.get("evtUsr").in("AA", "BB");
    
            Predicate[] predicateArray = {predicate1, predicate2, predicate3, predicate4, predicate5, 
                predicate6, predicate7, predicate8, predicate9, predicate10, predicate11};
            return query.where(predicateArray).getRestriction();
        };
        List<RetPrdInfo> retPrdInfoList = retPrdInfoDao.findAll(specification);
        retPrdInfoList.stream().forEach(System.out::println);
    }
    

    生成的sql语句:

    select 略
    from ret_prd_info
    where cr_tool_run_mode = ?
        and prd_seq_id <> ?
        and prd_qty > 1
        and prd_std_qty >= 1
        and prd_qty < 2
        and prd_qty <= 2
        and (evt_timestamp between ? and ?)
        and (prty is not null)
        and (cost_code is null)
        and (temp_prd_seq_id like ?)
      	and (retprdinfo0_.evt_usr in (?, ?));
    
    (2) 查询条件的连接

    1️⃣ ​写法一

    @Test
    public void testConjunctionSpecification1() {
        Specification<RetPrdInfo> specification = (root, query, cb) -> {
            Predicate predicate1 = cb.greaterThan(root.get("prdQty"), 1);
            Predicate predicate2 = cb.lessThan(root.get("prdQty"), 2);
            Predicate orPredicate1 = cb.or(predicate1, predicate2); // 使用or连接两个条件
    
            Predicate predicate3 = cb.greaterThanOrEqualTo(root.get("prdStdQty"), 1);
            Predicate predicate4 = cb.lessThanOrEqualTo(root.get("prdStdQty"), 2);
            Predicate orPredicate2 = cb.or(predicate3, predicate4); // 使用or连接两个条件
    
            Predicate andPredicate = cb.and(orPredicate1, orPredicate2);
    
            return query.where(andPredicate).getRestriction();      // 使用and连接两个条件
        };
        List<RetPrdInfo> retPrdInfoList = retPrdInfoDao.findAll(specification);
        retPrdInfoList.stream().forEach(System.out::println);
    }
    

    生成的sql语句:

    select 略
    from ret_prd_info
    where prd_qty > 1 and prd_qty < 2
         or prd_std_qty >= 1 and prd_std_qty <= 2
    

    where 后面如果有and、or的条件,则or自动会把左右的查询条件分开,即先执行and,再执行or。

    原因就是:and的执行优先级最高。 关系型运算符优先级高到低为:not and or

    2️⃣ 写法二

    @Test
    public void testConjunctionSpecification2() {
        Specification<RetPrdInfo> specification = (root, query, cb) -> {
            Predicate predicate1 = cb.greaterThan(root.get("prdQty"), 1);
            Predicate predicate2 = cb.lessThan(root.get("prdQty"), 2);
            Predicate[] orArray1 = {predicate1, predicate2};
            Predicate orPredicate1 = cb.and(orArray1);              // 用or连接数组中的每个条件
    
            Predicate predicate3 = cb.greaterThanOrEqualTo(root.get("prdStdQty"), 1);
            Predicate predicate4 = cb.lessThanOrEqualTo(root.get("prdStdQty"), 2);
            Predicate[] orArray2 = {predicate3, predicate4};
            Predicate orPredicate2 = cb.or(orArray2);               // 用or连接数组中的每个条件
    
            Predicate[] andArray = {orPredicate1, orPredicate2};
            Predicate andPredicate = cb.and(andArray);              // 用and连接数组中的每个条件
    
            return query.where(andPredicate).getRestriction();
        };
        List<RetPrdInfo> retPrdInfoList = retPrdInfoDao.findAll(specification);
        retPrdInfoList.stream().forEach(System.out::println);
    }
    

    3️⃣ 写法三

    @Test{
    public void testConjunctionSpecification3() {
        Specification<RetPrdInfo> specification = (root, query, cb) -> {
            Predicate predicate1 = cb.greaterThan(root.get("prdQty"), 1);
            Predicate predicate2 = cb.lessThan(root.get("prdQty"), 2);
    
            Predicate[] predicateArray = {predicate1, predicate2};
    
            return query.where(predicateArray).getRestriction(); // where中放入predicate数组,使用and连接数组中的每个条件
        };
        List<RetPrdInfo> retPrdInfoList = retPrdInfoDao.findAll(specification);
        retPrdInfoList.stream().forEach(System.out::println);
    }}
    

    生成的sql:

    select 略
    from ret_prd_info
    where prd_qty > 1
        and prd_qty < 2
    
    (3) 对查询结果排序
    @Test
    public void testOrder() {
        Specification<Customer> spec = (root, query, cb) -> cb.equal(root.get("crToolRunMode"), "CLN");
        Sort sort = Sort.by(Sort.Direction.DESC, "id");
        List<Customer> list = customerDao.findAll(spec, sort);
    }
    
    (4) 分页查询
    @Test
    public void testPagging() {
    	  Specification<Customer> spec = (root, query, cb) -> cb.equal(root.get("crToolRunMode"), "CLN");
        Pageable pageable = PageRequest.of(0, 20);
        // 可以使用`Pageable pageable = PageRequest.of(0, 20, Sort.by("evtTimestamp"));`实现分页+排序
        Page<Customer> page = customerDao.findAll(spec, pageable);
      	List<Customer> list = page.getContent();      // 该页的内容
        long totalElements = page.getTotalElements(); // 全表的记录数目
    }
    

    1.3.2 多条件查询示例(and、equal、like、sort、page)

    每一个Predicate就是一个条件

    public void queryByCondition(FbpbisdatI inTrx, FbpbisdatO outTrx) {
        FbpbisdatIA iary = inTrx.getIary().get(0);
    
        Specification<BisData> specification = (root, query, cb) -> {
            List<Predicate> predicateList = new ArrayList<>();
    
            if (StringUtils.isNotBlank(iary.getDataCate())) {
              predicateList.add(cb.equal(root.get("dataCate"), iary.getDataCate()));
            }
            if (StringUtils.isNotBlank(iary.getDataSeqId())) {
              predicateList.add(cb.like(root.get("dataSeqId").as(String.class), "%" + ia.getMdlId() + "%"));
              // `"%" + ia.getMdlId() + "%"` 可以改写为 `Util.liktStr(ia.getmdlId())`
            }
            // 略
    
            Predicate[] p = new Predicate[predicateList.size()];
            query.where(predicateList.toArray(p));
            return query.getRestriction();
        };
        Sort sort = Sort.by("dataCate", "dataId", "dataExt");
    
        List<BisData> bisDataList;
        // 判断是否需要分页
        if (Util.shouldPage(inTrx)) {
            Page<BisData> bisDataPage = bisDataDao.findAll(specification, PageRequest.of(inTrx.getPageNum() - 1, inTrx.getPageSize(), sort));
            // `PageRequest.of(inTrx.getPageNum() - 1, inTrx.getPageSize(), sort)`
            // 可以改写为 `Util.getPageable(inTrx, sort)`
    
            bisDataList = bisDataPage.getContent();
            long tblCnt = bisDataPage.getTotalElements();
    
            outTrx.setTblCnt(tblCnt);
        } else {
            bisDataList = bisDataDao.findAll(specification, sort);
        }
        List<FbpbisdatOA> oary = CloneUtil.cloneList(bisDataList, FbpbisdatOA::new);
        outTrx.setOary(oary);
    }
    

    1.3.3 子查询示例

    public void testSubSpecification() {
        Specification<RetPrdInfo> specification = (root, query, cb) -> {
            // 1).父查询条件
            Predicate parentPredicate = cb.equal(root.get("crToolRunMode"), "CLN");
    
            // 2).子查询
            Subquery<RetPrdAbn> subQuery = query.subquery(RetPrdAbn.class);
            Root<RetPrdAbn> subRoot = subQuery.from(RetPrdAbn.class);
    
            subQuery = subQuery.select(subRoot.get("prdSeqIdFk"));
    
            // 子查询的cb和父查询的cb相同
            Predicate subPredicate1 = cb.equal(subRoot.get("lotIdFk"), "LW0001");
            Predicate subPredicate2 = cb.equal(subRoot.get("lotSpltIdFk"), "00");
            subQuery.where(cb.and(subPredicate1, subPredicate2));
    
            // 3).将父查询条件和子查询合并
            Predicate parentAndSubConjunctPredicate = root.get("prdSeqId").in(subQuery);
    
            return query.where(parentPredicate, parentAndSubConjunctPredicate).getRestriction();
        };
    
        List<RetPrdInfo> retPrdInfoList = retPrdInfoDao.findAll(specification);
        retPrdInfoList.stream().forEach(System.out::println);
    }
    

    生成的sql语句:

    select
        略
    from 
        ret_prd_info
    where 
        cr_tool_run_mode = ?
        and (prd_seq_id in 
                 (
                    select prd_seq_id_fk
                    from ret_prd_abn
                    where lot_id_fk = ? and retprdabn1_.lot_splt_id_fk = ?
                 )
            )
    

  • 相关阅读:
    Python
    Python
    Python
    Python
    Python
    Python
    Scala核心编程_第01章_Scala概述
    与富婆讨论性,死亡与生活的意义
    python邮件发送给多人时,只有第一个人能收到的问题
    少年维特的烦恼
  • 原文地址:https://www.cnblogs.com/daheww/p/15333623.html
Copyright © 2011-2022 走看看