Specifications
创始人
2025-05-31 17:55:06

1.适用范围

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

1.1 JpaSpecificationExecutor接口方法介绍

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

1.2 Specification

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

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

toPredicate方法

Specification类通过实现抽象方法Predicate toPredicate(Root 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.3 示例

1.3.1 简单示例

(1) 编写查询条件
@Test
public void testSimpleCbSpecification() {Specification 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);// betweenTimestamp 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%");// inPredicate 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 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 > 1and prd_std_qty >= 1and prd_qty < 2and prd_qty <= 2and (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 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 retPrdInfoList = retPrdInfoDao.findAll(specification);retPrdInfoList.stream().forEach(System.out::println);
}

生成的sql语句:

select 略
from ret_prd_info
where prd_qty > 1 and prd_qty < 2or 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 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.or(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 retPrdInfoList = retPrdInfoDao.findAll(specification);retPrdInfoList.stream().forEach(System.out::println);
}

3️⃣ 写法三

@Test{
public void testConjunctionSpecification3() {Specification 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 retPrdInfoList = retPrdInfoDao.findAll(specification);retPrdInfoList.stream().forEach(System.out::println);
}}

生成的sql:

select 略
from ret_prd_info
where prd_qty > 1and prd_qty < 2
(3) 对查询结果排序
  • Sort sort = Sort.by("firstname").ascending().and(Sort.by("lastname").descending());

  • TypedSort person = Sort.sort(Person.class);
    Sort sort = person.by(Person::getFirstname).ascending().and(person.by(Person::getLastname).descending());
    
@Test
public void testOrder() {Specification spec = (root, query, cb) -> cb.equal(root.get("crToolRunMode"), "CLN");Sort sort = Sort.by(Sort.Direction.DESC, "id");List list = customerDao.findAll(spec, sort);
}
(4) 分页查询
@Test
public void testPagging() {Specification 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 page = customerDao.findAll(spec, pageable);List 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 specification = (root, query, cb) -> {List 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"), "%" + 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 bisDataList;// 判断是否需要分页if (Util.shouldPage(inTrx)) {Page 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 oary = CloneUtil.cloneList(bisDataList, FbpbisdatOA::new);outTrx.setOary(oary);
}

1.3.3 子查询示例

public void testSubSpecification() {Specification specification = (root, query, cb) -> {// 1).父查询条件Predicate parentPredicate = cb.equal(root.get("crToolRunMode"), "CLN");// 2).子查询Subquery subQuery = query.subquery(RetPrdAbn.class);Root 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 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_fkfrom ret_prd_abnwhere lot_id_fk = ? and retprdabn1_.lot_splt_id_fk = ?))

相关内容

热门资讯

4月广州消费品市场表现强劲 1-4月,随着消费品以旧换新等促消费政策持续发力和各类会展活动陆续开展,政策相关消费快速增长,升级类...
金价,又跌了! 人民财讯5月31日电,5月30日,COMEX黄金期货收跌0.92%,报3313.1美元/盎司。 从高...
10万吨改性项目!巴斯夫、金发... 【DT新材料】获悉,6月3日,沪市主板新股海阳科技将启动申购,上市在即! 资料显示,海阳科技前身为南...
湾财周报|大事记 比亚迪驳斥“... 一周大事记(5月26日-6月1日) 头条 比亚迪驳斥! 长城“车圈恒大论”是行业警示还是危言耸听?...
通源石油跌1.96%,成交额1... 5月30日,通源石油跌1.96%,成交额1.03亿元,换手率4.40%,总市值23.54亿元。 异动...
中国邮储银行浙江分行2025校... 点这里 ↑ 老满说高考 作者 l 老满 生涯规划师l 升学顾问l 拆书家 这是 老满说高考公众号 的...
公募基金规模首次突破33万亿元... 每经记者:肖芮冬 每经编辑:叶峰 天赐良基日报第654期 一、今日基金新闻速览 1、华润元大基金贾...
湾财周报 大事记 比亚迪驳斥“... 一周大事记(5月26日-6月1日)头条比亚迪驳斥!长城“车圈恒大论”是行业警示还是危言耸听?近日,关...
EL表达式JSTL标签库 EL表达式     EL:Expression Language 表达式语言     ...
关于测试,我发现了哪些新大陆 关于测试 平常也只是听说过一些关于测试的术语,但并没有使用过测试工具。偶然看到编程老师...
工信部、中汽协紧急发声!汽车“... 文/刘育英新一轮汽车价格战再起。近日,工信部、中汽协纷纷发声表示反对。工业和信息化部表示,将加大对汽...
3 ROS1通讯编程提高(1) 3 ROS1通讯编程提高3.1 使用VS Code编译ROS13.1.1 VS Code的安装和配置...
募资39亿,全亏光了,账上不到... 关于天然气,用户的感觉是价格一直在上涨,但很奇怪,不管怎么涨,天然气企业仍然亏,还亏得一塌糊涂。这是...
资阳房产评估公司 这是(tel-15828298733)整理的信息,希望能帮助到大家 在当今社会,随着经济的发展和城...
华桥汇利(中国)投资基金管理有... 今年第一季度,美国企业利润出现大幅下降,且面临着来自关税上升的持续压力,这一局面可能会在今年进一步加...
ESG 报告合规与鉴证:全球政... 在当下全球经济格局里,ESG(环境、社会和公司治理)已然成为衡量企业可持续发展能力的关键指标。随着全...
【Unity 手写PBR】Bu... 写在前面 前期积累: GAMES101作业7提高-实现微表面模型你需要了解的知识 【技...
与锤巨子生物的大嘴博士持股同一... 医美龙头巨子生物“成分争议”风波持续发酵。日前,美妆博主大嘴博士(香港大学化学博士郝宇)发文,质疑巨...
Linux之进程间通信 目录 进程间通信介绍 一、为什么要进行进程间通信? 二、进程间通信目的 三、进程间通信...
从“造城”到“留客”,文旅局长... 你有没有刷到最近各地文旅局局长全体“尬舞”的视频?领导们放下架子开始跳魔性舞蹈,这场舞的背后啊,可不...
Hazel引擎学习(十一) 我自己维护引擎的github地址在这里,里面加了不少注释,有需要的可以看...
孩子的教育金,分享3个「有效」... 点击 “简七读财” ,发送消息“ 理财小工具 ”免费领取“40个赚钱工具资源包”晚上好,我是简七编...
iZotope RX 10(专... iZotope RX 10是一款专业的音频修复和增强软件,具有音频修复工具、音频增强工...
我的docker随笔40:cl... 本文介绍 clickhouse 数据库的容器化部署。 起因 某项目需生产环境数据库,因...
透视一周牛熊股:最牛股路桥信息... 过去一周(5月26日—5月30日)A股三大指数集体下跌。截至5月30日收盘,上证指数报3347.49...
基于matlab创建地面固定雷... 一、前言此示例演示如何创建和显示包含地面固定雷达、转弯飞机、等速飞机和移动地面车辆的多平台方案。二、...
暗夜发光,独自闪耀,盘点网页暗... 众所周知,网页的暗黑模式可以减少屏幕反射和蓝光辐射,减少眼睛的疲劳感&#...
C语言-程序环境和预处理(2) 文章目录预处理详解1.预定义符号2.#define2.1#define定义的标识符2.2#defin...
MySQL数据库知识整理 MySQL数据库知识整理 MySQL事务详解 事务四大特性ACID 原子性(Atomi...
Docker基础篇——最全讲解 文章目录一、CentOS安装docker二、启动帮助类命令三、镜像命令1.名词概念2.常用命令2.1...