72、Spring Data JPA 的 Specification 动态查询

news2024/11/17 14:43:47

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
{
    @Autowired
    private 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 要 -1
        Pageable 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") 是否等于 name
                    Predicate 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.coloradmin.cn/o/1018567.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

外星人入侵游戏-(创新版)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

不同类型程序的句柄研究

先做一个winform程序&#xff1b;随便放几个控件&#xff1b; 用窗口句柄查看工具看一下&#xff1b;form和上面的每个控件都有一个句柄&#xff1b; 然后看一下记事本&#xff1b;记事本一共包含三个控件&#xff0c;各自有句柄&#xff1b; 这工具的使用是把右下角图标拖到要…

服务器迁移:无缝过渡指南

&#x1f337;&#x1f341; 博主猫头虎&#xff08;&#x1f405;&#x1f43e;&#xff09;带您 Go to New World✨&#x1f341; &#x1f984; 博客首页——&#x1f405;&#x1f43e;猫头虎的博客&#x1f390; &#x1f433; 《面试题大全专栏》 &#x1f995; 文章图文…

基于SSM+Vue的高校实验室管理系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用Vue技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

线程的方法(未完成)

线程的方法 1、sleep(long millis) 线程休眠&#xff1a;让执行的线程暂停一段时间&#xff0c;进入计时等待状态。 static void sleep(long millis):调用此方法后&#xff0c;当前线程放弃 CPU 资源&#xff0c;在指定的时间内&#xff0c;sleep 所在的线程不会获得可运行的机…

如何使用Java语言判断出geek是字符串参数类型,888是整数参数类型,[hello,world]是数组参数类型,2.5是双精度浮点数类型?

如何使用Java语言判断出geek是字符串参数类型&#xff0c;888是整数参数类型&#xff0c;[hello,world]是数组参数类型&#xff0c;2.5是双精度浮点数类型&#xff1f; Java是一种静态类型的编程语言&#xff0c;这意味着我们需要在编译时为变量指定具体的类型。但是&#xff…

web应用及微信小程序版本更新检测方案实践

背景&#xff1a; 随着项目体量越来越大&#xff0c;用户群体越来越多&#xff0c;用户的声音也越来越明显&#xff1b;关于应用发版之后用户无感知&#xff0c;导致用户用的是仍然还是老版本功能&#xff0c;除非用户手动刷新&#xff0c;否则体验不到最新的功能&#xff1b;这…

ICCV 2023 | MPI-Flow:从单视角构建的多平面图像中学习光流

ICCV 2023 | MPI-Flow&#xff1a;从单视角构建的多平面图像中学习光流 引言&#xff1a;主要贡献&#xff1a;Motivation&#xff1a;算法细节&#xff1a;Optical Flow Data GenerationIndependent Object MotionsDepth-Aware Inpainting 实验结果&#xff1a; 来源&#xff…

QT:使用分组框、单选按钮、普通按钮、标签、行编辑器、垂直分布、水平分布做一个小项目

widget.h #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QRadioButton> //单选按钮 #include <QGroupBox> //分组框 #include <QHBoxLayout> //水平布局 #include <QVBoxLayout> //垂直布局 #include <QPushButton>…

MFC主框架和视类PreCreateWindow()函数学习

在VC生成的单文档应用程序中&#xff0c;主框架类和视类均具有PreCreateWindow函数&#xff1b; 从名字可知&#xff0c;可在此函数中添加一些代码&#xff0c;来控制窗口显示后的效果&#xff1b; 并且它有注释说明&#xff0c; Modify the Window class or styles here by…

RHCSA 重定向、vim练习题

1.重定向练习题 (1)新建一个文件redirect.txt&#xff0c;并在其中写入20210804RHCSA,保存并退出 先输入命令 [rootlocalhost ~]# vim redirect.txt进入vim编辑器后&#xff0c;按快捷键“i”进入编辑模式&#xff0c;再写入数据&#xff0c;写完之后按“esc"键退出编辑…

python 采用selenium+cookies 获取登录后的网页

百度网页由于需要登陆手机短信验证。比较麻烦 这里我采用先人工登录百度账号&#xff0c;然后将百度账号的相关cookies保存下来 然后采用selenium动态登录网页 整体代码如下 from selenium import webdriverimport timeoptions webdriver.ChromeOptions()options.add_argu…

LLM 08-分布式训练

LLM 08-分布式训练 8.1 为什么分布式训练越来越流行 近年来&#xff0c;深度学习被广泛应用到各个领域&#xff0c;包括计算机视觉、语言理解、语音识别、广告推荐等。在这些不同的领域中&#xff0c;一个共同的特点就是模型规模越来越大&#xff0c;比如 GPT-3 模型的参数量达…

华为云云耀云服务器L实例评测|基于L实例使用Docker部署MySQL服务并连接MySQL—phpMyAdmin管理工具

文章目录 一、云耀云服务器产品优势1、智能不卡顿2、价优随心用3、上手更简单4、管理更省心 二、远程连接云耀云服务器L实例三、安装Docker、docker-compse1、docker安装2、docker-compose安装 四、方法① 使用Docker安装部署MySQL服务五、方法② 使用docker-compse安装部署MyS…

2023年 python结合excel实现快速画图(零基础快速入门)

目录 1.适用人群 2.环境配置 3.基本用法 3.1 数据读取 3.2 数据分析 3.3 数据组装 3.4 制表&#xff1a; 4.快速提升 5.效果展示 1.适用人群 电脑有python环境&#xff0c;会python基本使用&#xff0c;需要短时间内完成大量画图任务的数据分析的人群。&#xff08;有…

AlexNet——训练花数据集

目录 一、网络结构 二、创新点分析 三、知识点 1. nn.ReLU(inplace) 2. os.getcwd与os.path.abspath 3. 使用torchvision下的datasets包 4. items()与dict()用法 5. json文件 6. tqdm 7. net.train()与net.val() 四、代码 AlexNet是由Alex Krizhevsky、Ilya S…

深入学习 Redis Cluster - 基于 Docker、DockerCompose 搭建 Redis 集群,处理故障、扩容方案

目录 一、基于 Docker、DockerCompose 搭建 Redis 集群 1.1、前言 1.2、编写 shell 脚本 1.3、执行 shell 脚本&#xff0c;创建集群配置文件 1.4、编写 docker-compose.yml 文件 1.5、启动容器 1.6、构建集群 1.7、使用集群 1.8、如果集群中&#xff0c;有节点挂了&am…

基于springboot+vue的养老院管理系统 前后端分离项目

养老院管理系统 前后端分离项目 后端技术&#xff1a;springbootmybatis-plusredismysql 前端&#xff1a;vue3.0elementui-plus 【人员管理】 用户管理 客户管理【药品食品管理】 药品管理 食品管理【报修管理】【外出管理】外出管理 访客管理【留言管理】【新闻管理】新闻…

服务器杀掉死进程

清掉服务器上的死进程 查看服务器使用情况 nvidia-smi发现并没有显示进程&#xff0c;那应该是有死进程。 查看占用情况 ps aux会显示如上图的进程列表。 PID进程号STAT状态(S休眠R运行Z死掉了)TIME占用时间(过长的有问题)COMMAND进程启动命令(应该能帮助回忆起这是什么程…

IDEA——工程项目的两种窗口开发模式

文章目录 引言一、多项目窗口模式的便利1.1 源码 debug 二、多项目窗口模式的弊端三、多项目窗口的版本管理四、单项目、多项目窗口模式转换 引言 idea编辑器有两种窗口模式&#xff0c;一种是单项目窗口&#xff0c;另一种是多项目窗口。 我个人使用较多的是单项目窗口&#…