当前位置: 首页 > news >正文

新版本 Spring Data Jpa + QueryDSL 使用教程

Spring Data Jpa 提供了基于 Jpa 的抽象层,用于在 Spring 框架下访问数据库,同时也提供了许多简化开发的功能,比如分页、投影、审计等。但是它没有办法方便快捷地自定义查询条件,尤其是条件查询。
QueryDSL 提供了基于 Java 的同一查询抽象,简化了自定义查询条件的过程。但是现在 QueryDSL 官方的开发进度已经放缓,基本不再更新,不过好在社区自行 Fork 了一个版本(OpenFeign QueryDSL)。本文将介绍如何使用 QueryDSL 简化 Jpa 查询,开发语言以 Kotlin 为主,同时也会介绍使用 Java 时该如何实现。

引入 QueryDSL

Spring Data Jpa 引入 QueryDSL 的部分在 Spring Data Extensions 里面可以找到,这里直接给出 OpenFeign QueryDSL 的引入方法。
Maven:

<dependencies><dependency><groupId>io.github.openfeign.querydsl</groupId><artifactId>querydsl-jpa</artifactId><version>${querydslVersion}</version></dependency>
</dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><annotationProcessorPaths><!-- Explicit opt-in required via annotationProcessors orannotationProcessorPaths on Java 22+, see https://bugs.openjdk.org/browse/JDK-8306819 --><annotationProcessorPath><groupId>io.github.openfeign.querydsl</groupId><artifactId>querydsl-apt</artifactId><version>${querydslVersion}</version><classifier>jpa</classifier></annotationProcessorPath><annotationProcessorPath><groupId>jakarta.persistence</groupId><artifactId>jakarta.persistence-api</artifactId></annotationProcessorPath></annotationProcessorPaths><!-- Recommended: Some IDE's might require this configuration to include generated sources for IDE usage --><generatedTestSourcesDirectory>target/generated-test-sources</generatedTestSourcesDirectory><generatedSourcesDirectory>target/generated-sources</generatedSourcesDirectory></configuration></plugin></plugins>
</build>

Gradle(Kotlin语法):

dependencies {implementation "io.github.openfeign.querydsl:querydsl-jpa:${querydslVersion}"annotationProcessor "io.github.openfeign.querydsl:querydsl-apt:${querydslVersion}:jpa"annotationProcessor 'jakarta.persistence:jakarta.persistence-api'testAnnotationProcessor "io.github.openfeign.querydsl:querydsl-apt:${querydslVersion}:jpa"testAnnotationProcessor 'jakarta.persistence:jakarta.persistence-api'
}

如果使用 Java 的话,使用上面方法引入即可,或者使用原版 QueryDSL 也可以使用。不过使用原版可能与一些新的组件(比如 Kotlin KSP、Spring Modulith 等)不兼容,因此如果使用 Kotlin 的话推荐使用 OpenFeign 版本:

plugins {// 省略其他 Kotlin 或相关插件id("com.google.devtools.ksp") version "2.1.20-2.0.0"  
}dependencies {// 省略 Kotlin 和 Spring 等其他依赖// QueryDSLimplementation("io.github.openfeign.querydsl:querydsl-jpa:6.11")ksp("io.github.openfeign.querydsl:querydsl-ksp-codegen:6.11")
}kotlin {compilerOptions {freeCompilerArgs.addAll("-Xjsr305=strict")// 避免覆盖 QuerydslBinderCustomizer 方法后 Spring Data Jpa 尝试解析 customize 方法导致无法启动freeCompilerArgs.addAll("-Xjvm-default=all")}
}allOpen {annotation("jakarta.persistence.Entity")annotation("jakarta.persistence.MappedSuperclass")annotation("jakarta.persistence.Embeddable")
}

注意事项如下:

  • KSP 的版本可以参考 KSP 官方仓库 来获取最新版本。
  • 使用 Kotlin 时需要添加编译选项 -Xjvm-default=all,这个与另一个接口 QuerydslBinderCustomizer 有关,我们放在后面详细说明。
  • 将具有 EntityMappedSuperClassEmbeddable 注解的类配置为开放类,避免 Jpa 查询数据转换为 Kotlin 对象时报错。
    使用 OpenFeign QueryDSL 的话,还可以参考 QueryDSL Examples 上面提供的示例代码,获取其他技术栈的引入方式。

使用 QueryDSL

Spring Data Jpa 通过 QuerydslPredicateExecutor 接口提供了集成 QueryDSL 的方法。定义 Repository 时继承此接口即可。QuerydslPredicateExecutor 提供了如下几个方法:

  • findOne:根据查询条件查询一条数据,返回 Optional<T>,并在有多条数据时抛出异常。
  • findAll:根据查询条件(或/和自定义排序方式)查询所有的数据,返回 Iterable<T>
  • findAll:根据查询条件和分页参数查询所有数据,返回 Page<T>
  • count:根据查询条件返回符合条件的结果数量,返回 long
  • exists:根据查询条件判断是否有符合条件的结果,返回 boolean
  • findBy:通过查询条件,以及对后续结果的操作,实现自定义返回结果,这个方法支持查询投影、自定义排序、限制查询数量等操作,也能自定义返回结果,后续将重点进行介绍。

定义实体

我们定义如下的实体 User

@Entity
public class User(@Id@GeneratedValue(strategy = GenerationType.UUID)val id: String,val name: String,val age: Int
)

执行 gradle build 命令之后,QueryDSL 会自动为我们生成对应的 Q 类。对于 User 实体,就会生成 QUser 类,这是 QueryDSL 提供的定义查询的方式。
如果要查询名称包含 name,并且年龄大于 20 的用户,我们可以这样编写:

val user = QUser.user
val predicate = user.name.contains("name").and(user.age.gt(20))
// 或者
val predicate = BooleanBuilder().apply {and(user.name.contains("name"))and(user.age.gt(20))
}// UserRepository 需要继承 QuerydslPredicateExecutor
val users = userRepository.findAll(predicate)

com.querydsl.core.BooleanBuilder 是 QueryDSL 提供的组合查询逻辑的工具,具有下列方法:
and / or / not:逻辑与 / 或 / 非。
andAnyOf:接受 Predicate 的可变长参数,表示并且右侧任意一个符合条件。
orAllOf:表示或者右侧全部符合条件,同样接受 Predicate 可变长参数。
andNot / orNot:对于右侧的条件取反,然后进行逻辑与 / 或。

查询单条数据

查询单条数据有两种操作:一种是只查询一条,在有多条可选数据时报错;另一种不管有多少数据,只查询第一条。后者的操作可以通过 findBy 方法实现。
首先是使用 findOne 方法,只查询一条,返回 Optional<T>

val optionalUser: Optional<User> = userRepository.findOne(predicate)

之后是使用 findBy 方法,它的函数签名如下:

<S extends T, R> R findBy(Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction
);

其中各个参数如下:
TST 为实体类型,ST 的子类型,也可以是它本身。
Function<FluentQuery.FetchableFluentQuery<S>, R>:表示对于查询结果的后续操作。需要注意的是,queryFunction 接收的类型是 S,因此使用时需要手动标注类型(Kotlin 需要,Java 没有试过,不过如果类型不匹配,可以考虑标注上参数类型)。
R:表示返回结果类型,可能是 Optional<T>T?,也可能是分页、数量、是否存在等查询结果。

val user = userRepository.findBy(predicate) { query: FluentQuery.FetchableFluentQuery<User> ->query.one()
}

其他方法写法与这类似,因此直接给出函数和对应功能:
one:返回 Optional<T>,在有多条数据时抛出异常。
oneValue:同 one 方法,不过返回类型为 T?
first:返回 Optional<T>,在有多条数据时返回第一条。
firstValue:同 first 方法,不过返回类型为 T?
如果想要返回特定的一条数据,可以使用 FetchableFluentQuery.sortBy 方法,传入一个 Sort 类型的排序方法,先对查询出来的数据进行排序。

是否存在、查询数量

FetchableFluentQuerylong countboolean exists 方法,分别用来查询符合条件的数据数量,以及是否存在。

查询列表、分页

FetchableFluentQueryallpage 方法,可以用来查询符合条件的数据:
List<T> all:以列表的形式返回全部结果。
Page<T> page(Pageable pageable):以分页的形式返回全部结果。

自定义投影、限制数量等

如果只想查询部分行,或者部分列,可以考虑使用 limit 方法和 as / project 方法:
FetchableFluentQuery<T> limit(int limit):限制查询结果数量。
<R> FetchableFluentQuery<R> as(Class<R> resultType):将查询结果转换为类型为 R 的投影。
default FetchableFluentQuery<T> project(String... properties)FetchableFluentQuery<T> project(Collection<String> properties):接收多个属性名称,限制只查询一部分字段。
Spring Data Jpa 有两种类型的投影,分别使用接口和 Class 定义。需要注意的是,使用基于 Class 的投影时,无法使用嵌套投影(自定义属性的属性的类型)。
另外一个需要注意的点是,Spring Data Jpa 直到 3.5.0 版本(Spring Boot 3.5.0)开始,才正式支持 FetchableFluentQuery 使用基于 Class 的投影。在这之前,如果使用此功能,Spring Data Jpa 就会报错“只支持使用基于接口的投影方法”,详情参见 GitHub Issus。

通过 Controller 绑定查询参数

对于继承了 QueryDSL 的存储实现(不只是 Jpa、Spring Data MongoDB 等也受到支持),可以使用 QuerydslPredicate 将查询语句转换为 Predicate

// 下面是一个接口的查询方法,root 需要传入实体类的类型
fun query(@QuerydslPredicate(root = User::class) predicate: Predicate): Void {// ...
}

使用此功能时,需要使用 EnableSpringDataWebSupport 注解(加在启动类或者其他被自动装配的 Bean 上),并且要有 QueryDSL 依赖。
对于这一段 URL:?name=test&age=20,等价于下面的查询语句:

val predicate = BooleanBuilder().apply{and(QUser.user.name.eq("name"))and(QUser.user.age.eq(20))
}

默认的绑定行为如下:
Object 类型参数 + 简单类型属性:eq,例如 ?name=test
Object 类型参数 + 集合类型属性:contains,例如 ?name=a&name=b&name=c
Collection 类型参数 + 简单类型属性:in,例如 ?name=xxx
如果要自定义绑定行为,比如对于字符串类型默认模糊匹配等,可以让 Repository 接口继承 QuerydslBinderCustomizer<Q>,其中 Q 是 QueryDSL 生成的 Q 类型。

// 下面代码引用自 Spring Data Jpa 文档,并改为 Kotlin 语法
interface UserRepository: JpaRepository<User, String>,QuerydslPredicateExecutor<User>,QuerydslBinderCustomizer<QUser> {override fun customize(bindings: QuerydslBindings, user: QUser) {bindings.bind(user.username).first((path, value) -> path.contains(value))bindings.bind(String.class).first((StringPath path, String value) -> path.containsIgnoreCase(value))bindings.excluding(user.password)}
}

它的含义是:

  1. 对于 username 属性,传入参数时模糊匹配,但是区分大小写。
  2. 对于其他字符串类型的属性,传入参数时模糊匹配,不区分大小写。
  3. 不绑定对于 password 属性的查询参数。
    使用 Java 时,这里需要是默认方法。如果不声明为默认方法,那么 Spring Data Jpa 会将其视为一个查询方法,并尝试从中解析查询语句,在解析失败后报错,导致 Spring Boot 无法启动。
    使用 Kotlin 时,需要添加编译器选项,保证 Kotlin 编译为 Java 时生成默认方法:
kotlin {  compilerOptions {  // 省略其他编译器选项,比如 -Xjsr305=strict 等freeCompilerArgs.addAll("-Xjvm-default=all")  }  
}

关于 JVM Default 行为,可以参阅 Kotlin 文档。

http://www.lqws.cn/news/591625.html

相关文章:

  • TensorFlow源码深度阅读指南
  • 【科研绘图系列】基于R语言的复杂热图绘制教程:环境因素与染色体效应的可视化
  • C#程序设计简介
  • 9-2 MySQL 分析查询语句:EXPLAIN(详细说明)
  • Milvus docker-compose 部署
  • 从苹果事件看 ARM PC市场的未来走向
  • 2025年Java后端开发岗面试的高频项目场景题 + 八股文(100w字)
  • SAFNet:一种基于CNN的轻量化故障诊断模型
  • 【os】标准库
  • Rust 学习笔记:比较数值
  • 分布式锁——学习流程
  • 设计模式精讲 Day 20:状态模式(State Pattern)
  • 从零到一搭建远程图像生成系统:Stable Diffusion 3.5+内网穿透技术深度实战
  • 深入解析NumPy的核心函数np.array()
  • Linux 终止进程
  • 企业级应用技术-ELK日志分析系统
  • Text2SQL主流实现方案
  • js代码09
  • matlab/Simulink-全套50个汽车性能建模与仿真源码模型9
  • Next.js 安装使用教程
  • UniApp完全支持快应用QUICKAPP-以及如何采用 Uni 模式开发发行快应用优雅草卓伊凡
  • Spring Boot 启动加载执行链路分析
  • 基于Socketserver+ThreadPoolExecutor+Thread构造的TCP网络实时通信程序
  • 启用不安全的HTTP方法
  • 遥感影像岩性分类:基于CNN与CNN-EL集成学习的深度学习方法
  • 二十八、【环境管理篇】灵活应对:多测试环境配置与切换
  • Prompt生成指南
  • Gin 中间件详解与实践
  • AT6558R-5N32介绍
  • 阿里云-云效自动部署spring boot项目