ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Spring Data JPA 实战:@Query 与 JPQL 从入门到避坑

Spring Data JPA 实战:@Query 与 JPQL 从入门到避坑 在 Spring Boot 里Spring Data JPA 让我们写数据库访问代码变得非常轻松。简单查询可以用方法名派生比如findByEmail但一旦查询变复杂方法名就会又长又难维护。这时候Query和 JPQL 就该出场了。这篇文章会从基础到实战讲清楚JPQL 是什么Query怎么用参数绑定、分页、排序、投影、更新、删除原生 SQL 怎么配合常见坑和最佳实践一、JPQL 是什么JPQL 全称Java Persistence Query Language是 JPA 提供的一种面向对象的查询语言。它和 SQL 很像但关键区别是JPQL 操作的是实体类名和实体属性名SQL 操作的是数据库表名和列名例如java select u from User u where u.email :email这里的User是实体类名email是实体属性名不是数据库表名和列名。如果实体这样定义java Entity Table(name users) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; private String username; private String email; private Integer age; private Boolean active; private LocalDateTime createdAt; // getters / setters }那么 JPQL 写User而不是users写u.email而不是email列。JPA 会根据映射自动翻译成 SQL。二、Query 是什么Query是 Spring Data JPA 提供的注解用来在 Repository 接口方法上直接声明查询。它有两种模式JPQL 查询默认模式原生 SQL 查询需要加nativeQuery true基本用法java public interface UserRepository extends JpaRepositoryUser, Long { Query(select u from User u where u.email :email) OptionalUser findByEmail(Param(email) String email); }调用findByEmail(ab.com)时Spring Data JPA 会执行对应的 JPQL并返回结果。Query的最大价值是复杂查询不用再靠方法名硬拼也不用写实现类。三、参数绑定位置参数与命名参数1. 位置参数用?1、?2表示第几个参数java Query(select u from User u where u.username ?1 and u.age ?2) ListUser findByUsernameAndAge(String username, Integer age);缺点是参数一多可读性差容易错位。2. 命名参数推荐用:参数名配合Paramjava Query(select u from User u where u.username :username and u.age :age) ListUser findByUsernameAndAge(Param(username) String username, Param(age) Integer age);命名参数更清晰维护也更方便。四、常用查询示例1. 模糊查询不要写like %:keyword%这在 JPQL 中不合法。正确写法java Query(select u from User u where u.username like concat(%, :keyword, %)) ListUser searchByUsername(Param(keyword) String keyword);忽略大小写java Query(select u from User u where lower(u.username) like lower(concat(%, :keyword, %))) ListUser searchByUsernameIgnoreCase(Param(keyword) String keyword);2. IN 查询java Query(select u from User u where u.id in :ids) ListUser findByIds(Param(ids) CollectionLong ids);3. 关联属性查询java Query(select u from User u where u.address.city :city) ListUser findByCity(Param(city) String city);这里address.city是实体属性路径。4. 排序可以在 JPQL 里写order byjava Query(select u from User u where u.active true order by u.createdAt desc) ListUser findActiveUsersOrderByCreatedAtDesc();也可以交给Sort或Pageable。五、分页与排序Spring Data JPA 对分页支持很好。只要方法参数有Pageable返回Page或Slice即可java Query(select u from User u where u.active true) PageUser findActiveUsers(Pageable pageable);调用java Pageable pageable PageRequest.of(0, 10, Sort.by(createdAt).descending()); PageUser page userRepository.findActiveUsers(pageable);对于复杂 JPQL建议显式提供countQueryjava Query(value select u from User u where u.active true, countQuery select count(u) from User u where u.active true) PageUser findActiveUsers(Pageable pageable);原生 SQL 分页也类似java Query(value select * from users where active true, countQuery select count(*) from users where active true, nativeQuery true) PageUser findActiveUsersNative(Pageable pageable);六、返回类型与 DTO 投影Query不只可以返回实体还可以返回部分字段。1. 接口投影java public interface UserNameOnly { String getUsername(); String getEmail(); }java Query(select u.username as username, u.email as email from User u where u.active true) ListUserNameOnly findActiveUserNames();2. DTO 构造表达式java public record UserDto(Long id, String username) {} java Query(select new com.example.demo.dto.UserDto(u.id, u.username) from User u where u.active true) ListUserDto findActiveUserDtos();注意new后面要写 DTO 的全限定类名构造器参数要匹配。只查需要的字段可以减少数据传输和内存开销。七、更新与删除Query也可以执行update和delete但必须加Modifying通常还要加Transactional。更新java Modifying Transactional Query(update User u set u.active false where u.lastLogin :date) int deactivateInactiveUsers(Param(date) LocalDateTime date);返回int表示受影响行数。删除java Modifying Transactional Query(delete from User u where u.active false) int deleteInactiveUsers();如果更新后马上要查询同一批数据建议java Modifying(clearAutomatically true, flushAutomatically true)因为批量更新不会自动同步持久化上下文可能导致查询到旧数据。八、原生 SQLnativeQuery有些场景 JPQL 表达不了比如数据库特有函数、复杂 SQL、优化查询。这时可以用原生 SQLjava Query(value select * from users where email :email, nativeQuery true) OptionalUser findByEmailNative(Param(email) String email);但要记住nativeQuery true后写的是表名和列名不同数据库语法可能不兼容分页、排序、映射要更小心原则是能 JPQL 就 JPQL必要时才原生 SQL。九、Query vs 方法名派生 vs Specification场景推荐方式简单条件查询方法名派生复杂但固定查询Query JPQL动态多条件查询Specification/ QueryDSL数据库特有语法nativeQuery true更新删除QueryModifying方法名派生适合findByEmail、findByAgeGreaterThan这类简单查询。Query适合固定但复杂的查询。如果条件数量不确定比如后台筛选列表优先考虑Specification或 QueryDSL。十、常见坑与最佳实践常见坑把 JPQL 当 SQL 写JPQL 用实体名和属性名不是表名和列名。忘记Modifying更新、删除不加会报错。忘记事务Modifying操作通常需要Transactional。模糊查询写成%:keyword%应该用concat(%, :keyword, %)。返回单结果却用List或实体可能遇到NonUniqueResultException。单结果建议用Optional。原生 SQL 分页没写countQuery复杂查询可能生成错误 count SQL。实体名大小写问题默认实体名就是类名大小写敏感。更新后持久化上下文未同步考虑clearAutomatically true。最佳实践简单查询用方法名派生复杂固定查询用Query。优先 JPQL必要时再用原生 SQL。参数绑定优先用Param命名参数。只查需要字段用 DTO 或接口投影。分页查询显式提供countQuery。用join fetch解决 N1 问题java Query(select distinct u from User u join fetch u.roles where u.active true) ListUser findActiveUsersWithRoles();给 Repository 写集成测试别只靠启动成功。打开 SQL 日志确认最终执行的 SQL 符合预期。十一、总结Query是 Spring Data JPA 中非常强大的工具而 JPQL 是它默认使用的查询语言。一句话记住JPQL 是面向对象的查询语言写实体和属性。Query让你在 Repository 方法上直接声明 JPQL 或原生 SQL。简单查询用方法名派生复杂固定查询用Query动态条件用 Specification/QueryDSL。掌握Query和 JPQL能让你在 Spring Boot 中写出更清晰、更灵活、更可控的数据访问层。
返回列表