← 开发笔记

Spring Boot 3 与 Querydsl:Jakarta 配置和 Q 类型生成

本文目录

在 Spring Boot 3 上生成 Querydsl JPA 的 Q 类型时,应对齐 querydsl-jpa 和 querydsl-apt 的版本及 jakarta 分类器,配置注解处理,再检查干净构建与数据库查询测试。Querydsl 5.0 发布说明记录了 Jakarta 分类器支持。保留 Gradle 默认的生成源码路径,可避免主代码和测试编译写入同一目录。

本指南将最初的 2023 年韩文示例扩展为可运行配置。以下版本用于固定复现环境,并非当前生产依赖的推荐版本。

处理流程

生成 Q 类型并执行查询的流程

处理器在主代码编译时生成 QEmployee。随后 H2 测试使用生成的类型执行查询。

复现环境与完整 Gradle 配置

组件 复现版本
JDK / Gradle 17.0.8.1 / 8.4
Spring Boot BOM 3.5.7
Querydsl JPA / APT 5.1.0,均使用 jakarta 分类器
Hibernate / H2 6.6.33.Final / 2.3.232,由 BOM 管理

在新目录创建这五个文件。测试项目使用 Java 插件和 Boot BOM,不需要 Boot Gradle 插件。对于现有 Boot 应用,只在已有依赖管理基础上添加所需依赖。

settings.gradle

rootProject.name = 'querydsl-jakarta-example'

build.gradle

plugins { id 'java' }
repositories { mavenCentral() }
dependencies {
    implementation platform('org.springframework.boot:spring-boot-dependencies:3.5.7')
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta'

    annotationProcessor platform('org.springframework.boot:spring-boot-dependencies:3.5.7')
    annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta'
    annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
    annotationProcessor 'jakarta.annotation:jakarta.annotation-api'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testRuntimeOnly 'com.h2database:h2'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
test { useJUnitPlatform() }

Gradle 单独解析 annotationProcessor,与普通编译和运行时类路径分开。因此示例也将 BOM 应用于处理器配置,并显式提供 Jakarta Persistence 和 Annotation API。implementation 上的依赖不会自动对处理器可用。参见 Gradle Java 插件文档。

创建应用与实体

应用类让 JPA 测试能够发现 Boot 配置。测试无需另行启动服务进程。

src/main/java/example/Application.java

package example;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

src/main/java/example/Employee.java

package example;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    public String getName() { return name; }

    protected Employee() {}

    public Employee(String name) {
        this.name = name;
    }
}

Employee 使用 jakarta.persistence。不要手写 QEmployee:注解处理器会生成下面测试引用的类型。

通过生成的 Q 类型查询

src/test/java/example/EmployeeQueryTest.java

package example;

import com.querydsl.jpa.impl.JPAQueryFactory;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest;
import org.springframework.boot.test.autoconfigure.orm.jpa.TestEntityManager;

import static org.assertj.core.api.Assertions.assertThat;

@DataJpaTest
class EmployeeQueryTest {
    @Autowired
    private TestEntityManager entityManager;

    @Test
    void findsEmployeeByName() {
        entityManager.persistAndFlush(new Employee("Alice"));
        entityManager.persistAndFlush(new Employee("Bob"));
        QEmployee employee = QEmployee.employee;
        var names = new JPAQueryFactory(entityManager.getEntityManager())
            .select(employee.name).from(employee)
            .where(employee.name.eq("Alice")).fetch();
        assertThat(names).containsExactly("Alice");
    }

    @Test
    void returnsNoRowsForMissingName() {
        entityManager.persistAndFlush(new Employee("Alice"));
        QEmployee employee = QEmployee.employee;
        var names = new JPAQueryFactory(entityManager.getEntityManager())
            .select(employee.name).from(employee)
            .where(employee.name.eq("Missing")).fetch();
        assertThat(names).isEmpty();
    }
}

@DataJpaTest 使用此示例类路径中的 H2。每项测试在事务中运行,结束后回滚。用 persistAndFlush 插入数据后,生成的 QEmployee 支持两项检查:名称精确匹配和查询无结果。参见 Spring Boot JPA 测试指南。

这些测试仅检查 H2 上的此实体和查询,不验证生产数据库的 SQL 方言、排序规则、锁定或迁移行为。

运行并检查生成文件

选中 JDK 17 并使用已安装的 Gradle 8.4。在项目根目录运行:

java -version
gradle --version
gradle --no-daemon clean test

检查以下生成产物与测试报告:

build/generated/sources/annotationProcessor/java/main/example/QEmployee.java
build/classes/java/main/example/QEmployee.class
build/reports/tests/test/index.html

两项测试均在此配置下通过。再次执行 clean test 后重新生成了 Q 类型,两项测试再次通过。现有项目有 Gradle wrapper 时,通过 ./gradlew clean test 使用其选定版本。

此示例生成的 Q 类型导入 javax.annotation.processing.Generated,这是 Java SE 的 java.compiler API,无需替换为 Jakarta。JDK 17 Generated API。

为何 Q 类型缺失或重复生成?

症状 首先检查
找不到 QEmployee CLI 的 clean compileJava 结果、处理器依赖以及实体的 jakarta.persistence 导入。
处理器找不到 Persistence API 所需 Jakarta API 是否在独立于普通类路径的处理器路径上。
出现不兼容的 javax.persistence.EntityManager 类型 两个 Querydsl 制品的版本与 Jakarta 分类器是否匹配。
Q 类型重复,或仅在 clean 后构建成功 移除手写 Q 源码与共用生成路径;检查 main/test 输出配置。
CLI 通过但 IDE 看不到 Q 类型 Gradle 同步、委托构建设置与 IDE 注解处理。

示例未为生成文件添加自定义 sourceSets、JavaCompile 或 clean 配置。如需自定义路径,保持 main 和 test 输出分离。configureEach 控制延迟任务配置,无法修复 Jakarta 依赖兼容性。Gradle 配置规避。

升级的整体顺序参见 Spring Boot 3 迁移指南。出站 HTTP 边界请使用 WebClient 和 MockWebServer 测试。

更换查询工具前先比较

完整 Employee 类包含 getter,可将仓库结果转换为名称进行比较。添加 Querydsl 与 Specification 比较中的三个文件,可在相同实体上运行另外八项检查。上述原有两项测试仍属于十项测试项目。这里比较的是部分 H2 谓词,而非全部数据库行为。Spring Boot 指南串联周边的配置与测试步骤。

讨论