72、Spring Data JPA 的 Specification 动态查询

Specification:规范、规格

★ Specification查询

它也是Spring Data提供的查询——是对JPA本身 Criteria 动态查询 的包装。

▲ 为何要有动态查询

页面上常常会让用户添加不同的查询条件,程序就需要根据用户输入的条件,动态地组合不同的查询条件。JPA为动态查询提供了Criteria查询支持。Spring Data JPA则对Criteria查询进行了封装,封装之后的结果就是Specification查询。——Specification查询比Jpa的Criteria动态查询更加简单。

如图:
在这里插入图片描述

▲ 核心API: JpaSpecificationExecutor

  • long count(Specification spec): 返回符合Specification条件的实体的总数。
  • List findAll(Specification spec): 返回符合Specification条件的实体。
  • Page findAll(Specification spec, Pageable pageable): 返回符合Specification条件的实体,额外传入的Pageable参数用于控制排序和分页。
  • List findAll(Specification spec, Sort sort): 返回符合Specification条件的实体,额外传入的Sort参数用于控制排序。
  • Optional findOne(Specification spec): 返回符合Specification条件的单个实体,如果符合条件的实体有多个,该方法将会引发异常。

▲ Specification查询的步骤:

(1)让你的DAO接口继承JpaSpecificationExecutor 这个核心API。(2)构建Specification对象,用于以面向对象的方式来动态地组合查询条件。——最方便的地方(改进的地方),这一步就是为了解决动态拼接SQL的问题,而改为使用面向对象的方式来组合查询条件。

▲ 如何创建Specification对象(用于组合多个查询条件)

- Specification参数用于封装多个代表查询条件的Predicate对象。- Specification接口只定义了一个toPredicate()方法,该方法返回的Predicate对象就是Specification查询的查询条件,程序通常使用Lambda表达式来实现toPredicate()方法来定义动态查询条件。

▲ 还涉及如下两个API(本身就是来自于JPA的规范)

Predicate -  代表了单个查询条件,相当于sql语句的where子句中的单个的条件
(比如 age>100,就是一个Predicate )。
也可用于组合多个查询条件。CriteriaBuilder - 专门用于构建单个Predicate。

在这里插入图片描述

代码演示

  下面的代码演示也属于---组合多个查询条件:方式1:用Specification的 and 或 or来组合多个 Specification——每个Specification只组合一个查询条件。

需求1:查询名字和年龄都符合的条件–equal
在这里插入图片描述

简洁写法
在这里插入图片描述

需求2:查询名字是 沙 开头的,年龄大于 100的学生--------like
在这里插入图片描述

▲ 如何组合多个查询条件?

 两种方式:A - 用Specification的and或or来组合多个 Specification——每个Specification只组合一个查询条件。B - 先用CriteriaBuilder的and或or来组合多个Predicate对象,得到一个最终的Predicate,然后再将Predicate包装成Specification。

代码演示

需求:根据传来的student,如果该对象里面的某个属性不为null,就将该属性作为查询条件进行查询。

演示:先用CriteriaBuilder的and或or来组合多个Predicate对象,
得到一个最终的Predicate,然后再将Predicate包装成Specification。

下面的查询就是组合查询,

Predicate - 代表了单个查询条件,相当于sql语句的where子句中的单个的条件
(比如 age>100,就是一个Predicate )。也可用于组合多个查询条件。

CriteriaBuilder - 专门用于构建单个Predicate。
在这里插入图片描述
在这里插入图片描述

测试结果:
在这里插入图片描述

完整代码:

StudentDaoTest

package cn.ljh.app.dao;import cn.ljh.app.domain.Student;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;//SpringBootTest.WebEnvironment.NONE : 表示不需要web环境
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class StudentDaoTest
{@Autowiredprivate StudentDao studentDao;/*** @ValueSource: 每次只能传一个参数* @CsvSource:每次可以传多个参数*///需求:查询年龄大于指定参数的记录//参数化测试@ParameterizedTest@ValueSource(ints = {20, 200})public void testFindByAgeGreaterThan(int startAge){List<Student> students = studentDao.findByAgeGreaterThan(startAge);students.forEach(System.err::println);}//根据年龄和班级名称查询学生//Age 和 ClazzName 用 And 连接起来,表示两个查询条件,//ClazzName这两个单词中间没有And连接起来,表示是一个路径写法,表示是Clazz类的name属性@ParameterizedTest//参数一个是int,一个是String,这个注解在传参的时候会自动进行类型转换@CsvSource(value = {"20,超级A营", "18,超级D班"})public void testFindByAgeAndClazzName(int age, String clazzName){List<Student> students = studentDao.findByAgeAndClazzName(age, clazzName);students.forEach(System.err::println);}//pageNo: 要查询哪一页的页数 , pageSize: 每页显示的条数@ParameterizedTest@CsvSource({"洞,2,3", "洞,1,4", "洞,3,2"})public void testFindByAddressEndingWith(String addrSuffix, int pageNo, int pageSize){//分页对象,此处的pageNo是从0开始的,0代表第一页,所以这里的 pageNo 要 -1Pageable pageable1 = PageRequest.of(pageNo - 1, pageSize);Page<Student> students = studentDao.findByAddressEndingWith(addrSuffix, pageable1);int number = students.getNumber() + 1;System.err.println("总页数:" + students.getTotalPages());System.err.println("总条数:" + students.getTotalElements());System.err.println("当前第:" + number + " 页");System.err.println("当前页有:" + students.getNumberOfElements() + " 条数据");students.forEach(System.err::println);}//======================================测试 Specification 查询=======================================================//查询名字和年龄都符合的条件--equal@ParameterizedTest@CsvSource({"沙和尚,580"})public void testSpecificationQuery(String name, int age){/** root : 代表要查询的实体(就是 student)* criteriaBuilder:专门用于构建运算符的*/List<Student> students = studentDao.findAll(((Specification<Student>) (root, criteriaQuery, criteriaBuilder) ->{//判断 root.get("name") 是否等于 namePredicate p1 = criteriaBuilder.equal(root.get("name"), name);return p1;})//再次使用 and 添加了一个 Specification 的条件.and((root, criteriaQuery, criteriaBuilder) ->{Predicate p2 = criteriaBuilder.equal(root.get("age"), age);return p2;}));students.forEach(System.err::println);}//查询名字和年龄都符合的条件---简洁写法---equal@ParameterizedTest@CsvSource({"沙和尚,580"})public void testSpecificationQuery1(String name, int age){List<Student> students = studentDao.findAll(((Specification<Student>)(root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("name"), name)).and((root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("age"), age)));students.forEach(System.err::println);}//查询名字是 沙 开头的,年龄大于 100的学生--------like@ParameterizedTest@CsvSource({"猪%,100"})public void testSpecificationQuery2(String name, int age){List<Student> students = studentDao.findAll(((Specification<Student>)(root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.like(root.get("name"), name)).and((root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.gt(root.get("age"), age)));students.forEach(System.err::println);}//组合查询@ParameterizedTest//参数是一个对象,对象的数据从这个getStudent方法里面获取//该方法要求:1、该方法必须以 static 修饰,//该方法的返回值必须是 stream , Stream 中的数据必须是被测试方法要求的参数类型@MethodSource("getStudents")public void testSpecificationQuery3(Student student){//此处查询,只要 student 的哪个属性不为null,就查询哪些条件studentDao.findAll((Specification<Student>) (root, query, criteriaBuilder) ->{//使用 predicateList 来收集查询条件List<Predicate> predicateList = new ArrayList<>();//如果name属性不为null,就说明需要添加name作为查询条件if (student.getName() != null && !student.getName().equals("")){predicateList.add(criteriaBuilder.equal(root.get("name"), student.getName()));}//如果 age 属性不等于 0 ,就说明需要添加 age 作为查询条件if (student.getAge() != 0){predicateList.add(criteriaBuilder.equal(root.get("age"), student.getAge()));}//如果 address 属性不等于 null ,就说明需要添加 address 作为查询条件if (student.getAddress() != null && !student.getAddress().equals("")){predicateList.add(criteriaBuilder.equal(root.get("age"), student.getAge()));}//Gender 是char类型,如果 Gender 属性不等于 '\u0000'-->空字符串 ,就说明需要添加 Gender 作为查询条件if (student.getGender() != '\u0000'){predicateList.add(criteriaBuilder.equal(root.get("gender"), student.getGender()));}//由于 criteriaBuilder 的 and 方法的参数是数组,因此此处将 predicateList 集合转成 数组Predicate predicate = criteriaBuilder.and(predicateList.toArray(new Predicate[1]));return predicate;}).forEach(System.err::println);}//该方法要求:1、该方法必须以 static 修饰,//该方法的返回值必须是 Stream , Stream 中的数据必须是被测试方法要求的参数类型public static Stream<Student> getStudents(){Stream<Student> studentStream = Stream.of(new Student("孙悟空", 0, null, '\u0000', null),new Student("孙悟空", 500, null, '\u0000', null),new Student("孙悟空", 500, "花果山水帘洞", '\u0000', null),new Student("孙悟空", 500, "花果山水帘洞", '男', null),new Student("孙", 50, "花果山", '男', null));return studentStream;}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/111469.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ConfigMaps-2

文章目录 主要内容一.Volume 挂载 ConfigMap1.创建一个Pod&#xff0c;起挂载的内容&#xff0c;将来自下面的configmap&#xff1a;代码如下&#xff08;示例&#xff09;: 2.解释 二.环境变量 ConfigMap1.创建一个名为 mysqlpass 且包含 passwordABCabc123 的 configmap&…

操作系统期末复习笔记

文章目录 操作系统第1章 计算机系统概述1 指令执行的基本指令周期2 中断分类与中断处理过程2.1 中断的定义2.2 中断分类2.3 中断的意义2.4 无中断2.5 有中断2.6 中断和指令周期2.7 中断处理的过程 3 处理多中断的两种方法3.1 顺序中断处理&#xff08;禁止中断&#xff09;3.2 …

【ICASSP 2023】ST-MVDNET++论文阅读分析与总结

主要是数据增强的提点方式。并不能带来idea启发&#xff0c;但对模型性能有帮助 Challenge&#xff1a; 少有作品应用一些全局数据增强&#xff0c;利用ST-MVDNet自训练的师生框架&#xff0c;集成了更常见的数据增强&#xff0c;如全局旋转、平移、缩放和翻转。 Contributi…

STM32 ~ GPIO不同模式之间的区别与实现原理

GPIO全称General Purpose Input Output &#xff0c;即通用输入/输出。其实GPIO的本质就是芯片的一个引脚&#xff0c;通常在ARM中所有的I/O都是通用的。不过&#xff0c;由于每个开发板上都会设计不同的外围电路&#xff0c;这就造成了GPIO的功能可能有所不同。大部分GPIO都是…

GODIVA论文阅读

论文链接&#xff1a;GODIVA: Generating Open-DomaIn Videos from nAtural Descriptions 文章目录 摘要引言相关工作Video-to-video generationText-to-image generationText-to-video generation GODIVA方法逐帧视频自动编码器GODIVA视频生成器 实验数据集评价指标自动评估指…

java使用itext生成pdf

效果&#xff1a; maven依赖 <!--PDF处理--><!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf --><dependency><groupId>com.itextpdf</groupId><artifactId>itextpdf</artifactId><version>5.5.13.3</vers…

CH573-09-BLE蓝牙安卓应用二次开发——RISC-V内核BLE MCU快速开发教程

一、基础工程搭建 在上一章最后一讲的BLE蓝牙例程中&#xff0c;我们使用了沁恒官方的BLE调试助手完成数据发送&#xff0c;接下来我们使用Android Studio完成一款简易的BLE调试助手。 1、参考文章 我这里参考了CSDN中的一位博主“摸爬滚打的程序媛”的文章以及对应文章中的…

《动手学深度学习 Pytorch版》 6.6 卷积神经网络

import torch from torch import nn from d2l import torch as d2l6.6.1 LeNet LetNet-5 由两个部分组成&#xff1a; - 卷积编码器&#xff1a;由两个卷积核组成。 - 全连接层稠密块&#xff1a;由三个全连接层组成。模型结构如下流程图&#xff08;每个卷积块由一个卷积层、…

SQL 性能优化总结

文章目录 一、性能优化策略二、索引创建规则三、查询优化总结 一、性能优化策略 1. SQL 语句中 IN 包含的值不应过多 MySQL 将 IN中的常量全部存储在一个排好序的数组里面&#xff0c;但是如果数值较多&#xff0c;产生的消耗也是比较大的。所以对于连续的数值&#xff0c;能用…

网络安全攻防对抗之隐藏通信隧道技术整理

完成内网信息收集工作后&#xff0c;渗透测试人员需要判断流量是否出得去、进得来。隐藏通信隧道技术常用于在访问受限的网络环境中追踪数据流向和在非受信任的网络中实现安全的数据传输。 一、隐藏通信隧道基础知识 &#xff08;一&#xff09;隐藏通信隧道概述 一般的网络通…

正则表达式学习和高级用法

以下所有的验证都在 在线验证 1. 起始符 / 正则表达式的起始符2. 限定符 匹配前面的子表达式**1次或多次**。例如&#xff0c;zo 能匹配 "zo" 以及"zoo"&#xff0c;但不能匹配 "z"。等价于 {1,}。 ? 匹配前面的子表达式**0次或1次**。例如…

在c#中使用CancellationToken取消任务

目录 &#x1f680;介绍&#xff1a; &#x1f424;简单举例 &#x1f680;IsCancellationRequested &#x1f680;ThrowIfCancellationRequested &#x1f424;在控制器中使用 &#x1f680;通过异步方法的参数使用cancellationToken &#x1f680;api结合ThrowIfCancel…