Spring Data JPA 的 Specifications动态查询

摘要:
Specification类实现API。通过继承JpaSpecificationExecutor接口,可以使用Specification进行查询。只能返回dao定义的实体。即,仅从1.1中选择*JpaSpecificationExecutor接口方法简介:publicinterfaceJpaSpecifiationExecutor<T>{//查询单个对象:可选<T>findOne;//查询列表:list<T>findAll;//逐页查询所有;您可以使用PageRequest#of方法创建可分页对象,以实现逐页+分页<T>查找所有;//查询所有,排序list<T<findAll;//查询统计信息:longcount;}1.2规范类规范类是用于构建动态查询条件的抽象类。˃查询,CriteriaBuilderbuilder);}1.2.1 toPredicte方法简介Specification类实现了抽象方法PredictoPredicte(Root<T>Root,CriteriaQuery<?

目录

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包下,其继承关系如下:

Spring Data JPA 的 Specifications动态查询第1张

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 = ?
             )
        )

免责声明:文章转载自《Spring Data JPA 的 Specifications动态查询》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇bladex中flowable中配置监听器别声张/丘特切夫(俄罗斯)下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

随便看看

zabbix监控华为交换机

xmlversion=“1.0”encoding=“UTF-8”?...

QT学习之如何在QToolBar中添加带图标的QToolButton并设置图标大小

在网上查到了三种方法,找到一种比较好理解的。图标存放位置可在工程文件夹里创建自命名的文件夹如"res",再在根目录下创建qrc文件,如图:然后我们需要对qrc文件进行编辑:res/1.pngres/2.pngres/3.pngres/4.pngres/5.pngres/6.pngres/7.png这里的"res"是自己命名的存放图标的目录。接着我们需要在项目...

SQL Server 查看版本信息

SQLServer查看版本信息3种方法:1)使用命令行查看[Win+R]键-˃打开cmd2)使用SSMS查看打开并连接SSMS后查看3)通过服务器属性查看使用SSMS打开并连接指定数据库后,查看服务器属性...

TCP UDP (转)

在互联网的早期,NCP协议用于主机之间的互连。该协议本身存在许多缺陷,例如:无法互连不同的主机,无法互连不同操作系统,并且没有纠错功能。为了改善这个缺点,Daniel提出了TCP/IP协议。现在几乎所有的操作系统都实现了TCP/IP协议栈。TCP/IP协议栈主要分为四层:应用层、传输层、网络层和数据链路层。每个层都有相应的协议。如下图所示,所谓的协议是双方之...

IntelliJ Idea 2019.1.3永久激活方式,JAR文件分享

用法:1.下载压缩包并将其解压缩以获取jetbrains代理Jar,将其放在您认为合适的文件夹中。2.修改idea64.exe。IntelliJIdea安装目录的bin文件夹中的vmoptions文件。若它是一个32位系统,那个么它就符合这个想法。exe Vmoptions文件。在文件末尾添加一行:-javaagent:E:/ProgramFiles/ide...

解决curl: (35) OpenSSL SSL_connect: Connection reset by peer in connection to raw.githubusercontent.com:443 错误

报告命令curl-o时出错-https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh| bash错误状态的原因是未安装git。使用以下命令安装git,然后执行上面的命令sudoapt-geinstallgit-referencehttps://www.pianshen.com/articl...