使用 JPQL 编写自定义查询:基于 Spring Boot 和 MySQL 的实战指南

在这篇文章中,我们将深入探讨如何利用 Spring Boot 和 JPA 执行自定义 JPQL 查询。但我们要做的不仅仅是基础的 CRUD,我们将站在 2026 年的技术前沿,结合最新的 AI 辅助开发理念,构建一个健壮、高性能且易于维护的数据访问层。无论你是使用 IntelliJ IDEA 的强大控制台,还是偏爱 pgAdmin 的数据库视图,我们都将覆盖到。

2026 开发环境准备:从传统到 AI 原生

在我们编写第一行代码之前,让我们先聊聊现在的开发环境发生了怎样的变化。你可能已经注意到,自从 Cursor、Windsurf 和 GitHub Copilot 等 AI IDE 普及以来,我们的编码方式——也就是所谓的“Vibe Coding”(氛围编程)——已经发生了根本性的转变。我们不再死记硬背复杂的查询语法,而是更多地扮演架构师和审查者的角色,让 AI 成为我们最忠实的结对编程伙伴。

Step 1: 创建一个具备现代化特性的 Spring Boot 项目

首先,我们需要一个坚实的基础。虽然 Spring Initializr 依然经典,但在 2026 年,我们更倾向于直接在 IDE 中通过自然语言提示生成项目骨架。

让我们访问 Spring Initializr 并配置我们的项目:

  • Project: Maven
  • Language: Java 21 (利用虚拟线程提升并发性能)
  • Spring Boot: 3.5.x (确保包含最新的 Jakarta EE 持久化 API)
  • Java Version: 21

依赖项选择:

我们不仅需要 Spring Data JPA 和 MySQL Driver,为了应对现代微服务的挑战,我们通常会勾选 Validation (用于参数校验) 和 Actuator (用于可观测性)。

pom.xml 配置解析

在我们生成的 INLINECODEaffa5d4e 中,务必注意 INLINECODE66b78ef9 的引用。这是现代 Java 生态系统的标准。



    
    
        org.springframework.boot
        spring-boot-starter-data-jpa
    
    
    
    
        com.mysql
        mysql-connector-j
        runtime
    
    
    
    
        org.projectlombok
        lombok
        true
    

深入核心:配置与实体建模

Step 2: 数据源配置与最佳实践

配置文件不再仅仅是连接字符串的堆砌。在 2026 年,我们非常看重“可观测性”和“性能调优”。让我们来看看一个生产级的 application.yml 配置。

# application.yml
spring:
  application:
    name: jpql-demo
  datasource:
    url: jdbc:mysql://localhost:3306/jpql_demo_db?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
    username: root
    password: password # 生产环境请使用 Vault 或环境变量
    # HikariCP 是 Spring Boot 2.0+ 的默认连接池,性能极佳
    hikari:
      maximum-pool-size: 10 # 根据你的并发需求调整
      minimum-idle: 5
      connection-timeout: 30000
  jpa:
    hibernate:
      ddl-auto: update # 开发环境用 update,生产环境必须用 validate 或 none
    show-sql: true # 方便调试,查看生成的 SQL 到底长什么样
    properties:
      hibernate:
        format_sql: true # 格式化 SQL 输出,便于阅读
        dialect: org.hibernate.dialect.MySQLDialect
        # 开启统计信息,这对于我们后期监控性能瓶颈至关重要
        generate_statistics: true

Step 3: 实体设计——不仅是数据映射

我们在创建实体时,应该考虑到“安全性”和“审计”。让我们完善一下 Student1 实体。在实际的企业级项目中,你绝对不会只希望有三个字段。

package com.example.jpql.demo;

import jakarta.persistence.*;
import lombok.Data; // 使用 Lombok 简化代码
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;

import java.time.LocalDateTime;

@Entity
@Table(name = "students") // 明确指定表名是个好习惯
@Data // Lombok 自动生成 Getters/Setters/toString/equals
public class Student1 {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false, length = 100)
    private String name;

    private String course;

    private int age;

    // 现代应用必备:审计字段
    @CreationTimestamp
    @Column(updatable = false)
    private LocalDateTime createdAt;

    @UpdateTimestamp
    private LocalDateTime updatedAt;

    // 标准构造器
    public Student1() {}

    public Student1(String name, String course, int age) {
        this.name = name;
        this.course = course;
        this.age = age;
    }
}

核心部分:构建与执行自定义 JPQL 查询

Step 4: Repository 层的演变

这是本文的重点。我们不再满足于简单的 findAll()。我们将定义一个自定义查询,根据课程名称、学生姓名和年龄进行复杂的筛选。在 2026 年,我们推荐结合注解和 SpEL (Spring Expression Language) 来增强查询的安全性。

StudentRepository.java:

package com.example.jpql.demo;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

public interface StudentRepository extends JpaRepository {

    // 
    // 方法 1: 使用 JPQL 和 @Query 注解
    // 注意:Student1 是实体名,不是表名 (students)!这是 JPQL 的核心概念。
    //
    @Query("SELECT s FROM Student1 s WHERE " +
           "(:course IS NULL OR s.course = :course) AND " +
           "(:name IS NULL OR s.name LIKE %:name%) AND " +
           "(:minAge IS NULL OR s.age >= :minAge)")
    List findByFilters(@Param("course") String course,
                                 @Param("name") String name,
                                 @Param("minAge") Integer minAge);

    // 
    // 方法 2: 使用原生 SQL (当你觉得 JPQL 性能不足时)
    // 注意:nativeQuery = true
    //
    // @Query(value = "SELECT * FROM students WHERE course = ?1", nativeQuery = true)
    // List findStudentsByCourseNative(String course);
}

Step 5: 使用 Workbench 或 pgAdmin 验证逻辑

在编写 Java 代码之前,作为经验丰富的开发者,我们会先在数据库客户端验证逻辑。你可能会问:“为什么要这样做?”

因为,如果在 SQL 层面逻辑不通,Java 包装得再好也没用。

  • 打开 pgAdmin 或 MySQL Workbench。
  • 连接到 jpql_demo_db
  • 将 JPQL 翻译成 SQL 进行测试。

假设我们要查询“计算机科学”课程中年龄大于 20 岁的学生。

* JPQL 思维: SELECT s FROM Student1 s WHERE s.course = ‘CS‘ AND s.age > 20

* SQL 验证 (在 Workbench 中执行):

        SELECT * FROM students WHERE course = ‘CS‘ AND age > 20;
        

Expert Tip: 如果你发现 SQL 查询很慢,比如全表扫描,先在数据库加个索引,然后再回到 Java 代码。不要试图在 Java 层面解决数据库层面的性能瓶颈。

-- 在 Workbench 中运行,优化查询性能
CREATE INDEX idx_student_course ON students(course);
CREATE INDEX idx_student_age ON students(age);

进阶主题:2026 年视角的企业级实践

1. 动态查询与 Criteria API

有时候,查询条件过于复杂,比如前端传来的搜索表单有 10 个可选字段。写一大堆 IF 语句在 JPQL 字符串里简直是噩梦。

在处理这种场景时,我们可以使用 JpaSpecificationExecutor。这让我们的查询逻辑变得动态且类型安全。

// 扩展 Repository 接口
public interface StudentRepository extends JpaRepository, JpaSpecificationExecutor {
    // ... 原有方法
}

// 在 Service 层构建动态查询
// 这不仅代码整洁,而且能有效防止 SQL 注入
public List searchStudents(String name, Integer age) {
    return repository.findAll((root, query, cb) -> {
        List predicates = new ArrayList();
        
        if (name != null) {
            predicates.add(cb.like(root.get("name"), "%" + name + "%"));
        }
        if (age != null) {
            predicates.add(cb.greaterThan(root.get("age"), age));
        }
        
        return cb.and(predicates.toArray(new Predicate[0]));
    });
}

2. 性能陷阱与优化策略

在我们的职业生涯中,最常见的性能杀手莫过于 N+1 问题

场景: 你有一个 INLINECODE9c4bd897 实体和一个 INLINECODE89212e6c 实体(一对多关系)。当你查询所有学生时,Hibernate 可能会发出 1 条 SQL 查所有学生,然后每遇到一个学生,再发一条 SQL 去查他的课程。如果有 100 个学生,就是 101 条 SQL!
解决方案 (2026 版):

在 JPQL 中使用 Entity GraphJOIN FETCH

// 使用 JOIN FETCH 强制抓取关联数据,合并为一次查询
@Query("SELECT s FROM Student1 s JOIN FETCH s.courseDetail WHERE s.age > :minAge")
// 注意:前提是你在实体中定义了 @OneToMany 或 @ManyToOne 关系
List findAdultStudentsWithCourses(@Param("minAge") int age);

3. AI 辅助调试

当你遇到 SyntaxErrorException: unexpected token 时,不要盯着屏幕发呆。直接把报错信息和你的 JPQL 查询扔给 Cursor 或 GitHub Copilot。

你可以这样问:“这是我的实体类定义,这是我的 JPQL 查询,为什么报错?请帮我根据 MySQL 8.0 的方言修正它。”

你会发现,AI 能够在几秒钟内指出你可能忽略了的大小写问题(JPQL 对实体名和属性名大小写敏感)或是路径导航错误(如 INLINECODE7a55fa4f 写成了 INLINECODE1a47cb5a)。

总结

在这篇文章中,我们不仅重温了如何编写基础的 JPQL 查询,更重要的是,我们探讨了在 2026 年作为一名全栈工程师应有的思维方式。我们了解了如何利用 Workbench 验证逻辑,如何编写具备容错性的动态查询,以及如何避免经典的 N+1 性能陷阱。

现在,让我们打开 IntelliJ IDEA,运行这个应用,并观察控制台输出的 SQL 语句。如果你看到的是格式整齐、执行高效的 SQL,那么恭喜你,你已经掌握了数据库交互的现代艺术。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/22480.html
点赞
0.00 平均评分 (0% 分数) - 0