秒懂SpringBoot之如何使用Spring Data JPA中Specification进行动态查询

[版权申明] 非商业目的注明出处可自由转载
出自:shusheng007

文章目录

  • 概述
    • JPA是什么?
    • Spring Data JPA 是什么?
    • Specifications 是什么?
  • Specifications 使用场景
  • 如何使用
    • 引入依赖
    • 创建Entity
    • 创建Repository
    • 创建对应的Specification
    • 使用
    • 使用Metamodel
      • 配置`jpamodelgen`工具
  • 总结
  • 源码

概述

操作数据库大概是一个web程序最重要的部分了,而Spring Data JPA 正是spring生态中用来解决此问题的利器。今天让我们简单聊聊这个话题。

首先让我们来理清楚一些关键概念:

JPA是什么?

JPA (Java Persistence API) 是 Java 平台上的一种标准化的 ORM(对象-关系映射)规范。JPA 提供了一种面向对象的数据访问模型,允许开发人员将 Java 对象映射到数据库表,从而实现对象关系映射。

JPA是Java平台定义的一种规范,一个抽象层。所有人都可以来实现它,但是我们现在一般时候的都是Hibernate。其实除了Hibernate还有很多种JPA的实现方案,例如EclipseLinkOpenJPABlaze-Persistence 等,感兴趣的小伙伴可以继续探索。

Spring Data JPA 是什么?

Spring Data JPA 是Spring Data 项目的一部分,它提供了对 JPA的更高级别的抽象,旨在简化 JPA 的使用。它利用了 JPA 规范,并提供了一组工具和功能,使得在 Spring 应用程序中使用 JPA 更加容易和便捷。

Spring Data JPA 的主要目标是减少开发人员需要编写的重复代码,同时提供一致的数据访问方式。通过 Spring Data JPA,开发人员可以通过定义接口来声明查询方法,而无需手动编写实现。Spring Data JPA 将根据方法名称自动生成查询,从而简化了数据访问层的开发。

Specifications 是什么?

Specifications是Spring Data JPA的一部分,其是对 JPA中Criteria API 的一种包装。

Specifications 使用场景

当我们需要根据用户输入或者只有在runtime时才能确定的条件来查询数据库时就可以考虑使用。

让我们回想一下如何使用Spring Data JPA来操作数据库呢?如下所示

@Repository
public interface JpaStudentRepository extends JpaRepository<Student, Integer>{// 使用JPQL@Query("select s from Student s where s.number = ?1")Optional<Student> findByNumber(String number);//使用方法名称Optional<Student> findByName(String name);
}

创建一个接口并继承JpaRepository后,在其内部声明要操作数据库的方法即可。这块又有两种方案,第一种是使用JPQL或者 SQL,第二种就是使用方法名称,这个方法名称不能瞎写,是有一套自己的规则。

我首次接触这玩意儿的时候也比较懵逼,这你妈谁知道咋写啊?不用担心,软件行业都发展到现在这个地步了,你遇到的问题很多人都遇到过了,这不一个特别好用的工具就凌空出世了:Jpa-buddy。这是一个增值付费的IntelliJ IDEA插件,但是其免费功能已经满足我们大部分的日常需求了。

安装插件后,我们只需要在对应的JpaRepository对象一下,然后就会弹出如下的弹窗,然后选择你要使用的方法,点击后会弹出弹窗,选择查询条件后 jpa-buddy就在对应的JpaRepository下帮你生成了查询方法。

在这里插入图片描述

如何使用

那么如何使用Specification呢?

引入依赖

假设你已经完成了数据库相关的依赖和配置

 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency>

创建Entity

我们有一张学生表和一张老师表,老师和学生是多对多关系。

@Setter
@Getter
@Entity
@Table(name = "student", schema = "jpa-learn")
public class Student extends AbstractAuditingEntity {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)@Column(name = "id", nullable = false)private Integer id;@Column(name = "stu_name")private String name;@Column(name = "stu_number")private String number;@Column(name = "age")private Integer age;...@ManyToMany(cascade = {CascadeType.ALL})@JoinTable(name = "student_teacher_relation",joinColumns = @JoinColumn(name = "student_id"),inverseJoinColumns = @JoinColumn(name = "teacher_id"))private List<Teacher> teachers;}

创建Repository

创建Repository接口并扩展JpaSpecificationExecutor<T> 接口

@Repository
public interface JpaStudentRepository extends JpaRepository<Student, Integer>, JpaSpecificationExecutor<Student> {@Query("select s from Student s where s.number = ?1")Optional<Student> findByNumber(String number);Optional<Student> findByName(String name);
}

当我们extendsJpaSpecificationExecutor<Student> 接口后,就获得了很多以Specification为入参的方法,例如

List<T> findAll(Specification<T> spec);

接下来重头戏来了,我们要如何创建Specification然后传递到这些方法里面去。

创建对应的Specification

Specification是什么呢?你可以简单的理解为一个Specification就是一个查询条件,我们可以组合这些查询条件。

例如Specification1是:查询姓王的学生,Specification2是:年龄18岁的学生。那么Specification1.and(Specification2) 就是查询所有年龄为18岁且姓王的学生。

首先,Specification是一个接口,其只包含一个抽象方法,所以创建一个Specification只需要实现一个方法即可,如下所示:

public interface Specification<T> extends Serializable {@NullablePredicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);...
}

其中方法中的3个参数的含义如下:

假设有如下SQL:selcte * from student AS s where s.name = 'ss007'

  • root

代表我们的Entity,通过它来获取Entiy中的属性,对应SQL中的s

  • query

代表当前正在创建的查询。对应SQL中的selcte * from student AS s where s.name = 'ss007'

  • criteriaBuilder

代表构建查询条件的builder。对应SQL中的where s.name = 'ss007'

一般情况下我们会在一个工具类里创建很多个Specification,在然后再动态地将其组合在一起完成复杂的动态查询。

@UtilityClass
public class StudentSpecification {// 以学生名称查询public static Specification<Student> hasNumber(String number) {return new Specification<Student>() {@Overridepublic Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {return criteriaBuilder.equal(root.get(Student_.number), number);}};}//以学生年龄查询并以年龄排序public static Specification<Student> ageBetween(Integer minAge, Integer maxAge) {return (root, query, criteriaBuilder) -> {Predicate between = criteriaBuilder.between(root.get(Student_.age), minAge, maxAge);query.orderBy(criteriaBuilder.desc(root.get(Student_.age)));return between;};}//以学生的老师查询public static Specification<Student> hasTeacher(String teacher) {return (root, query, criteriaBuilder) -> {Join<Student, Teacher> stuTeachers = root.join(Student_.teachers);return criteriaBuilder.equal(stuTeachers.get(Teacher_.name), teacher);};}}

上面的代码我们定义了3个Specification,也就是3个查询条件,其含义注释已经说的很清楚了。

使用

我们使用上面创建的Specification来构建一个动态查询的方法,这里顺便实现了分页。

@Slf4j
@Service
public class MyServiceImpl implements MyService {@Autowiredprivate JpaStudentRepository studentRepository;@Overridepublic Page<Student> filterStudents(FilterStudentRequest filter, Pageable pageable) {Specification<Student> spec = Specification.where(null);if (StrUtil.isNotBlank(filter.getName())) {spec = spec.and(StudentSpecification.nameLike(filter.getName()));}if (!Objects.isNull(filter.getAgeMin()) && !Objects.isNull(filter.getAgeMax())) {spec = spec.and(StudentSpecification.ageBetween(filter.getAgeMin(), filter.getAgeMax()));}if (StrUtil.isNotBlank(filter.getNumber())) {spec = spec.and(StudentSpecification.hasNumber(filter.getNumber()));}if (StrUtil.isNotBlank(filter.getTeacher())) {spec = spec.and(StudentSpecification.hasTeacher(filter.getTeacher()));}return studentRepository.findAll(spec, pageable);}
}

如代码所示,我们可以根据用户的输入条件不断的组合Specification(查询条件),最后通过findAll方法查询返回。

使用Metamodel

细心的同学可能已经发现了在构建Specification时使用了一个Student_的类,这个类哪里来的呢?这个类其实是通过jpamodelgen工具生成的对应Entity的元数据,内如类似下面这样。

@StaticMetamodel(Student.class)
@Generated("org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
public abstract class Student_ extends top.ss007.jpademo.entity.AbstractAuditingEntity_ {/*** @see top.ss007.jpademo.entity.Student#number**/public static volatile SingularAttribute<Student, String> number;...public static final String NUMBER = "number";...
}

那为什么要使用Metamodel呢?为了类型安全,如果不使用Metamodel,那么就会再查询硬编码,例如

criteriaBuilder.equal(root.get("number"), number);

一旦number写错了,或者数据表字段重命名了就会会引发运行时错误,当上线后报错了就不好玩了,引入Metamodel后就会将这些问题在编译时暴露出来,保证了类型安全。

那如何使用Metamodel呢?

配置jpamodelgen工具

  • 配置依赖
<dependency><groupId>org.hibernate.orm</groupId><artifactId>hibernate-jpamodelgen</artifactId><version>${jpamodelgen.version}</version>
</dependency>
  • 配置APT
<plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>${maven.plugin.version}</version><configuration><annotationProcessorPaths><path><groupId>org.hibernate.orm</groupId><artifactId>hibernate-jpamodelgen</artifactId><version>${jpamodelgen.version}</version></path></annotationProcessorPaths></configuration>
</plugin>

编译后就会生产各个Entity对应的Metamodel数据。

总结

没接触Spring Data JPA 的时候觉得MyBatis和MyBatis plus真香,后来工作需要被迫使用了JPA,刚开始觉得变扭,但用熟悉了感觉也不错。人啊,走出自己的舒适区真的很难,所以那些能不断走出自己舒适区的同学真的很厉害,特别是在我们IT行业,这种能力更是弥足珍贵。虽然人善变,但男人有一样特别专一,就是至始至终都喜欢

源码

一如既往,你可以从本文首发获取源码

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

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

相关文章

【面经】interrupt()、interrupted()和isInterrupted()的区别与使用

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;面经 ⛺️稳中求进&#xff0c;晒太阳 interrupt方法 如果打断线程正在sleep&#xff0c;wait&#xff0c;join会导致被打断的线程抛出InterruptedException&#xff0c;并清除打断标记。如…

HTML - 你如何使H5页面禁止手动缩放

难度级别:初级及以上 提问概率:40% 我们知道,这道题其实是在考察meta标签的viewport属性,正常情况下设置viewport的代码为 <head><meta name="viewport" content="width=device-width,initial-scale=1.0" …

华为笔记本屏幕忽暗忽亮或者比较暗的问题

1、有时候会发现笔记本用着用着就暗了&#xff0c;然后又亮了&#xff0c;又或者屏幕很暗。除了笔记本的电源设计外&#xff0c;有可能是英特尔处理器的问题。 2、解决方式&#xff1a; &#xff08;1&#xff09;输入“英特尔”&#xff0c;找到&#xff0c;英特尔显卡控制中…

idea Springboot校园新闻系统VS开发mysql数据库web结构java编程计算机网页源码maven项目

一、源码特点 springboot 校园新闻发布系统是一套完善的信息系统&#xff0c;结合springboot框架和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用springboot框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库&a…

typora怎么切换主题

typora怎么切换主题 概述 看到Typora原始的主题都不太好看&#xff0c;于是下载添加一些更库的主题吧! &#xff08;1.有梯子的情况下&#xff09; 1、首先去Typora的官网&#xff0c;在首页的右上方看到主题选项点击。这里直接点击这里即可&#xff1b; 2、跳转到这个页面…

微电网优化:基于​海象优化算法(Walrus Optimization Algorithm,WOA)​的微电网优化(提供MATLAB代码)

一、微电网优化模型 微电网是一个相对独立的本地化电力单元&#xff0c;用户现场的分布式发电可以支持用电需求。为此&#xff0c;您的微电网将接入、监控、预测和控制您本地的分布式能源系统&#xff0c;同时强化供电系统的弹性&#xff0c;保障您的用电更经济。您可以在连接…

LeetCode-热题100:118. 杨辉三角

题目描述 给定一个非负整数 numRows&#xff0c;生成「杨辉三角」的前 numRows 行。 在「杨辉三角」中&#xff0c;每个数是它左上方和右上方的数的和。 示例 1: 输入: numRows 5 输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]] 示例 2: 输入: numRows 1 输出: [[1]]…

Android Studio学习10——资源res的使用

一、String,StringArray的使用 一次修改&#xff0c;多出生效 String StringArray 二、color的使用 颜色代码对应表 和上面的相似用法 三、Dimen(尺寸)的使用 用的少&#xff0c;一般直接写尺寸 四、如何写一个drawable作为背景 五、如何写一个可以改变的drawable(按钮按下…

Android 代码自定义drawble文件实现View圆角背景

简介 相信大多数Android开发都会遇到一个场景&#xff0c;给TextView或Button添加背景颜色&#xff0c;修改圆角&#xff0c;描边等需求。一看到这样的实现效果&#xff0c;自然就是创建drawble文件&#xff0c;设置相关属性shap&#xff0c;color&#xff0c;radius等。然后将…

matlab使用教程(35)—求解时滞微分方程(3)

1中立型 DDE 以下示例说明如何使用 ddensd 求解中立型 DDE&#xff08;时滞微分方程&#xff09;&#xff0c;其中时滞出现在导数项中。此问题最初由 Paul [1] 提出。方程是&#xff1a; 由于该方程在 y ′ 项中存在时滞&#xff0c;因此该方程称为中立型 DDE。如果时滞仅出现…

每日五道java面试题之ZooKeeper篇(三)

目录&#xff1a; 第一题. 会话管理第二题. 服务器角色第三题. Zookeeper 下 Server 工作状态第四题. 数据同步第五题. zookeeper 是如何保证事务的顺序一致性的&#xff1f; 第一题. 会话管理 分桶策略&#xff1a;将类似的会话放在同一区块中进行管理&#xff0c;以便于 Zoo…

Dockerd的使用

端口映射 存储卷 类似于mount&#xff0c;把真机的某个目录映射都容器里面 -v 选项可以有多个 利用存储卷修改配置文件 容器间网络模式 共享网络为 --networkcontainer&#xff1a;容器名 微服务架构 一种由容器为载体&#xff0c;使用多个小型服务组合来构建复杂的架构为…