Spring Data JPA Criteria查询、部分字段查询

news2024/9/20 23:47:03

前言

在上一篇SpringBoot集成JPA及基本使用-CSDN博客,里面讲解了通过Spring Data JPA的命名规范实现数据库查询以及自定义SQL语句查询。而在开发中,不定个数的多条件查询是一种很常见的场景,如根据注册起止日期、用户名、用户级别等查询用户,且其中的条件并不是必须填写的。使用Criteria查询可以高效的解决以上问题。在开始讲解之前,先了解一下JPQL。

JPQL语言

JPQL语言(Java Persistence Query Language)是一种和SQL非常类似的中间性和对象化查询语言,它最终会被编译成具体的地场数据库的SQL语言,从而屏蔽不同的数据库的差异。

JPQL是面向对象进行查询的语言,开发者可以通过访问持久化映射的实体类,以及类中的属性来编写类似SQL的语句。

JPQL语言通过Query接口封装执行,在Query中封装了数据库访问操作的相关方法。

JPA元模型

在JPA中,标准的查询是以元模型的概念为基础的。元模型是以具体持久化单元的受管实体定义的,实体可以是实体类、嵌入式类或映射的父类。提供受管实体元信息的类就是元模型类。使用元模型最大优势是可以在编译时访问实体的持久属性。

如上一篇SpringBoot集成JPA及基本使用-CSDN博客中定义的ProductEntity实体类对应的元模型类的名称为ProductEntity_,类中的属性全部是使用publict和static修饰的,类型为SingularAttribute。如下:

package com.jingai.jpa.dao.entity;

import java.util.Date;
import javax.annotation.Generated;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(ProductEntity.class)
public abstract class ProductEntity_ {

	public static volatile SingularAttribute<ProductEntity, Date> validateTime;
	public static volatile SingularAttribute<ProductEntity, Date> createTime;
	public static volatile SingularAttribute<ProductEntity, String> name;
	public static volatile SingularAttribute<ProductEntity, String> deliveryNo;
	public static volatile SingularAttribute<ProductEntity, String> securityCode;
	public static volatile SingularAttribute<ProductEntity, Long> pid;
	public static volatile SingularAttribute<ProductEntity, String> customer;
	public static volatile SingularAttribute<ProductEntity, Integer> validateNum;

	public static final String VALIDATE_TIME = "validateTime";
	public static final String CREATE_TIME = "createTime";
	public static final String NAME = "name";
	public static final String DELIVERY_NO = "deliveryNo";
	public static final String SECURITY_CODE = "securityCode";
	public static final String PID = "pid";
	public static final String CUSTOMER = "customer";
	public static final String VALIDATE_NUM = "validateNum";

}

元模型并不需要手动创建,hibernate提供了自动生成的插件,只需引入依赖及相应的配置即可直动生成。

2.1 引入依赖

        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <scope>provided</scope>
        </dependency>

2.2 在Idea中配置

本人使用的是

此时会在项目的target/generated-sources/annotations目录中自动生成对应添加@Entity实体类对应的元模型类。

如果显示的格式有问题,导致在代码中无法访问元模型类,可以设置annotations为Source Root。

2.3 Source Root设置

对Source Root不了解的,可以看一下

IDEA新建文件时没有找到java类文件_idea新建java类不见了-CSDN博客

元模型自动生成的设置不止一种,具体见https://docs.jboss.org/hibernate/jpamodelgen/1.3/reference/en-US/html/chapter-usage.html

Criteria查询

还是以上一篇的Product为例,根据创建的起止日期、名称以及客户名进行查询。

3.1 创建一个通用的分页查询的表单

package com.jingai.jpa.common.form;

import org.springframework.util.StringUtils;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

public class SearchBaseForm {

    private static final DateFormat FORMAT = new SimpleDateFormat("yyyy-MM-dd");
    // 起始日期
    private String startDate;
    // 截止日期
    private String endDate;
    // 当前查询的页,从1开始
    private Integer pageIndex;
    // 每页的记录数
    private Integer pageSize;

    public Integer getPageIndex() {
        return pageIndex == null || pageIndex == 0 ? 1 : pageIndex;
    }

    public Integer getPageSize() {
        return pageSize == null || pageSize == 0 ? 20 : pageSize;
    }

    public Date getStartDate() {
        try {
            return StringUtils.hasText(startDate) ? FORMAT.parse(startDate) : null;
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public Date getEndDate() {
        try {
            return StringUtils.hasText(endDate) ? FORMAT.parse(endDate) : null;
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public void setEndDate(String endDate) {
        this.endDate = endDate;
    }

    public void setPageIndex(Integer pageIndex) {
        this.pageIndex = pageIndex;
    }

    public void setPageSize(Integer pageSize) {
        this.pageSize = pageSize;
    }

    public void setStartDate(String startDate) {
        this.startDate = startDate;
    }
}

3.2 创建Product的查询表单

package com.jingai.jpa.common.form;

import lombok.Data;

@Data
public class ProductForm extends SearchBaseForm {

    private String name;
    private String customer;

}

3.3 Repository的修改

在上一篇的ProductRepository继承了JpaRepository,而这里要改为继承JpaRepositoryImplementation,其他不变。其中JpaRepositoryImplementation继承了JpaRepository,还继承了JpaSpecificationExecutor。此处的例子中需要用到JpaSpecificationExecutor的findAll()接口。

package com.jingai.jpa.dao;

import com.jingai.jpa.dao.entity.ProductEntity;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.jpa.repository.support.JpaRepositoryImplementation;

import java.util.List;

public interface ProductRepository extends JpaRepositoryImplementation<ProductEntity, Long> {

    List<ProductEntity> findByPidBetween(long startPid, long endPid);

    @Query("from ProductEntity where name like ?1")
    List<ProductEntity> searchByName(String name);

}

3.4 Criteria分页查询

    @Override
    public Page<ProductEntity> listByPage(ProductForm form) {
        // 创建一个Specification,实现接口中的toPredicate()方法,该方法返回一个Predicate
        Specification<ProductEntity> specification = new Specification<ProductEntity>() {
            @Override
            public Predicate toPredicate(Root<ProductEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                List<Predicate> predicates = new ArrayList<>(8);
                if(StringUtils.hasText(form.getName())) {
                    predicates.add(criteriaBuilder.like(root.get(ProductEntity_.NAME), "%" + form.getName() + "%"));
                }
                if(StringUtils.hasText(form.getCustomer())) {
                    predicates.add(criteriaBuilder.like(root.get(ProductEntity_.customer), "%" + form.getCustomer() + "%"));
                }
                if(form.getStartDate() != null) {
                    predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(ProductEntity_.createTime), form.getStartDate()));
                }
                if(form.getEndDate() != null) {
                    predicates.add(criteriaBuilder.lessThanOrEqualTo(root.get(ProductEntity_.createTime), form.getEndDate()));
                }
                return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
            }
        };
        // 创建排序字段,可设置多个
        Sort sort = Sort.by(Sort.Direction.DESC, ProductEntity_.createTime.getName());
        Pageable pageable = PageRequest.of(form.getPageIndex(), form.getPageSize(), sort);
        // 使用JpaSpecificationExecutor的findAll()方法,只能返回实体类的集合
        return productRepository.findAll(specification, pageable);
    }

Specification接口中的toPredicate()接收三个参数,分别为Root<T> root、CriteriaQuery<?> query、CriteriaBuilder criteriaBuilder。

CriteriaBuilder:用于构造条件查询、复合选择、表达式、谓词和排序
CriteriaQuery:定义了特定于顶级查询的功能,包含了查询的各个部分。如:select结果集、where条件、group by、order by等。在CriteriaQuery指定返回值结果集。
Root:定义Criteria查询的根对象,Criteria查询的根定义为实体类型,它与SQL查询中的FROM子句类似,定义了查询的FROM子句中能够出现的类型。可以有多个查询根对象

通过CriteriaBuilder提供的like、equal、lessThan、greaterThan等方法,返回Predicate对象,作为CriteriaQuery的where条件。

Predicate过滤条件应用到SQL语句的where子句中。在Criteria查询中,查询条件通过Predicate或Expression实例应用到CriteriaQuery对象上。

Predicate实例也可以使用Expression的isNull、isNotNull、in方法获取,复合Predicate语句可以使用CriteriaBuilder的and、or、andnot方法构建。

部分字段查询

在CriteriaQuery中,可以通过select()方法设置返回值集合,但使用JpaSpecificationExecutor的findAll()方法,只能返回实体类的集合,所以即使设置了也是返回ProductEntity实体。针对部分字段的查询,需要根据返回值信息,创建对应的CriteriaQuery对象。以下为查询部分字段的代码。

    @Override
    public Page<Object[]> listByPage2(ProductForm form) {
        // 获取一个builder
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        // createQuery中的传参为搜索结果的返回值类型,也就是结果集的泛型为Object[]数组
        CriteriaQuery<Object[]> query = builder.createQuery(Object[].class);
        // 查询的根对象,可以有多个查询根对象
        Root<ProductEntity> root = query.from(ProductEntity.class);
        // 设置查询的返回值信息。此处只查询pid和name
        query.multiselect(root.get(ProductEntity_.pid), root.get(ProductEntity_.name));
        // 添加搜索条件Predicate
        List<Predicate> predicates = new ArrayList<>(4);
        if(StringUtils.hasText(form.getName())) {
            predicates.add(builder.like(root.get(ProductEntity_.NAME), "%" + form.getName() + "%"));
        }
        if(StringUtils.hasText(form.getCustomer())) {
            predicates.add(builder.like(root.get(ProductEntity_.customer), "%" + form.getCustomer() + "%"));
        }
        if(form.getStartDate() != null) {
            predicates.add(builder.greaterThanOrEqualTo(root.get(ProductEntity_.createTime), form.getStartDate()));
        }
        if(form.getEndDate() != null) {
            predicates.add(builder.lessThanOrEqualTo(root.get(ProductEntity_.createTime), form.getEndDate()));
        }
        Predicate[] predicateses = predicates.toArray(new Predicate[0]);
        // 设置搜索条件
        query.where(predicateses);
        // 设置排序规则
        query.orderBy(new OrderImpl(root.get(ProductEntity_.createTime), false));
        // 执行分页查询
        TypedQuery<Object[]> query1 = entityManager.createQuery(query);
        query1.setFirstResult((form.getPageIndex()) * form.getPageSize());
        query1.setMaxResults(form.getPageSize());
        List<Object[]> list = query1.getResultList();

        // 获取总记录数
        // 设置新的查询返回值类型
        CriteriaQuery<Long> queryCount = builder.createQuery(Long.class);
        // 查询的根对象
        root = queryCount.from(ProductEntity.class);
        // 设置返回值信息,查询count(pid)
        queryCount.select(builder.count(root.get(ProductEntity_.pid)));
        // 设置查询条件
        queryCount.where(predicateses);
        Long count = entityManager.createQuery(queryCount).getSingleResult();
        // 返回分页查询信息
        Pageable pageable = PageRequest.of(form.getPageIndex(), form.getPageSize());
        return new PageImpl<Object[]>(list, pageable, count);
    }

针对分页查询,需要执行两次查询,一次是查询数据,另一次是查询总记录数。

结尾

Spring Data JPA的知识点还有很多,限于篇幅,本篇先分享到这里。

关于本篇内容你有什么自己的想法或独到见解,欢迎在评论区一起交流探讨下吧。

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

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

相关文章

HTTP网络协议,接口请求的内容类型 content-type(2024-04-27)

1、简介 Content-Type&#xff08;内容类型&#xff09;&#xff0c;一般是指网页中存在的 Content-Type&#xff0c;用于定义网络文件的类型和网页的编码&#xff0c;决定浏览器将以什么形式、什么编码读取这个文件&#xff0c;这就是经常看到一些 PHP 网页点击的结果却是下载…

【MySQL 5.7安装时候 出现2503报错,解决方案】

MySQL5.7 安装遇 2503问题如何解决 1.能正常安装就点这里2.出现2503问题就看这2.1先看问题2.1.1在官网下载好安装包后&#xff0c;首先先确认安装包是否完整&#xff0c;排除安装包损坏的问题2.1.2 安装时候出现这个2503问题 2.2上解决方案2.2.1 打开任务管理器2.2.2 解决 1.能…

python按时间分割日志

背景介绍 现在的项目都是RotatingFileHandler,指定每个文件大小,指定日志文件的份数。有一个缺点就是要看某一天的日志,需要把这一天之前的最后一份日志和这一天之后的第一份日志都拷贝下来,很多不是自己想要的内容。 需求 想要一个按时间,最好能按天分割日志。查看Pyth…

C语言入门课程学习笔记3

C语言入门课程学习笔记3 第12课 - if 语句编程练习第13课 - switch 多分支选择语句第14课 - 程序中的循环结构第15课 - while 语句编程练习第16课 - do...while 与 for第17课 - break 与 continue 本文学习自狄泰软件学院 唐佐林老师的 C语言入门课程&#xff0c;图片全部来源于…

解锁文件管理新境界:自定义命名与大写扩展名,让文件井然有序

在数字化时代&#xff0c;文件管理成为我们日常生活和工作中不可或缺的一部分。然而&#xff0c;面对杂乱无章的文件名和扩展名&#xff0c;我们时常感到无从下手。如何轻松掌握文件管理&#xff0c;让您的文件井然有序呢&#xff1f;今天&#xff0c;就让我们一起探索自定义命…

Innodb底层原理与Mysql日志机制到底怎么个事???

在学完Innodb底层原理与Mysql日志机制&#xff0c;自己进行总结&#xff0c;画了一张脑图&#xff0c;思路清晰许多 希望对大家也能有点帮助

大模型微调:技术迭代与实践指南

在人工智能领域&#xff0c;大模型&#xff08;LLM&#xff09;的微调是一个关键过程&#xff0c;它使模型能够适应特定的任务和数据集。微调是深度学习中用于改进预训练模型性能的重要技术。通过在特定任务的数据集上继续训练&#xff0c;模型的权重被更新以更好地适应该任务。…

MySQL-查询数据-练习

练习 1.创建一个查询&#xff0c;显示收入超过 12,000 的雇员的名字和薪水。 select LAST_NAME,SALARY from employees where SALARY > 12000;2.创建一个查询&#xff0c;显示雇员号为 176 的雇员的名字和部门号。 select LAST_NAME,DEPARTMENT_ID from employees where …

【win10相关】更新后出现未连接到互联网的问题及解决

问题背景 在win10更新完系统之后&#xff0c;第二天电脑开机后&#xff0c;发现无法上网&#xff0c;尝试打开百度&#xff0c;但是出现以下图片&#xff1a; 经过检查&#xff0c;发现手机是可以上网的&#xff0c;说明网络本身并没有问题&#xff0c;对防火墙进行了一些设置…

MySQL 之 主从复制

1. 主配置文件&#xff08;win下是my.ini&#xff0c;linux下是my.cnf&#xff09; #mysql 服务ID,保证整个集群环境中唯一 server-id1 #mysql binlog 日志的存储路径和文件名 log-bin/var/lib/mysql/mysqlbin #错误日志,默认已经开启 #log-err #mysql的安装目录 #basedir #mys…

基于Springboot的甘肃旅游服务平台(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的甘肃旅游服务平台&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构…

连接oracle时出现ORA-12541:TNS:无监听程序的错误

遇到个问题&#xff0c;有一台windows serve 的服务器&#xff0c;这台服务器&#xff08;只部署了oracle&#xff09;忽然监听出问题了&#xff0c;提示 一、问题检查步骤&#xff1a; 1.winR--->cmd--->输入 lsnrctl status 查看监听的状态 如果监听器未运行&#…

实用指南:如何在CMD中运行Java程序并快速修复错误

引言 Java&#xff0c;在企业级开发到教育学习的不同场合&#xff0c;这门历史悠久的编程语言一直占据着举足轻重的地位。编程过程中的报错和异常处理是每个开发者必须面对的挑战。不管是经验丰富的开发人员还是编码新手&#xff0c;理解和解决这些错误总是能带来提高。 本文旨…

开发总结-Controller层

Controller层一定要try catch一下&#xff0c;不然里面报的错可能导致程序报错。 catch中就表示有错误就 Return ResultUtils.err(e.getMessage()) 必填项校验 在实体属性中添加注解 NotNull : 用在基本类 型上 不能为null 但可以为空字符串 NotEmpty : 用在集合类上 不能为…

MySQL数据库基础(数据库的基本操作、常用的数据类型、表的相关操作)

前言 今天我们将介绍数据库的基本操作、常用的数据类型、表的相关操作 一、数据库的基本操作 1.1 显示当前的数据库 操作代码 show databases;1.2 创建数据库 基本语法&#xff1a; 1. //创建数据库 create database examble;2. create database if not exists exist exa…

PostgreSQL大版本如何升级?

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

Paddle OCR v4 微调训练文字识别SVTRNet模型实践

文字识别步骤参考&#xff1a;https://github.com/PaddlePaddle/PaddleOCR/blob/main/doc/doc_ch/recognition.md 微调步骤参考:https://github.com/PaddlePaddle/PaddleOCR/blob/release/2.7.1/doc/doc_ch/finetune.md 训练必要性 原始模型标点符号和括号容易识别不到 数据…

FIR滤波器——DSP学习笔记三(包含一个滤波器设计的简明案例)

​​​​​​ 背景知识 FIR滤波器的特性与优点 可精确地实现线性相位响应&#xff08;Linear phase response&#xff09;&#xff0c;无相位失真&#xff1b; 总是稳定的&#xff0c;所有极点都位于原点 线性相位FIR滤波器的性质、类型及零点位置 冲击响应满足&#xff1a;奇…

Java中的File类

File类概述和构造方法 File&#xff1a;它是文件和目录路径名的抽象表示 文件和目录是可以通过File封装成对象的 对于File而言&#xff0c;其封装的并不是一个真正存在的文件&#xff0c;仅仅是一个路径名而已&#xff0c;它可以存在&#xff0c;也可以不存在 我们对Fie的操…

通过maven命令行mvn的方式,下载依赖jar包

目录 目标步骤执行mvn命令 目标 有时通过idea-maven-reload all maven projects更新项目依赖时&#xff0c;会报错Could not find artifact xxx.xx:xxx.x:xxx.jar (https://repo1.maven.org/maven2/org/)。 此时可尝试通过mvn命令行进行依赖下载&#xff08;需要配置maven本地…