导读:本期聚焦于俊华创作的《Spring Data JPA 中如何使用 @Query 注解编写原生 SQL 和 JPQL 复杂查询》,敬请观看详情。当 Spring Data JPA 提供的方法命名查询无法满足多表关联、聚合统计、动态排序这类复杂需求时,@Query 注解就成了最直接的解决方案。本文详细讲解 @Query 的基本用法,对比 JPQL 与原生 SQL 两种写法的差异和适用场景,并深入展开命名参数绑定、SpEL 参数引用、分页排序整合、Modifying 更新操作以及返回 DTO 投影等进阶技巧,同时提醒使用中容易踩到的坑,比如原生 SQL 分页时 Sort 会失效、更新语句忘记加事务注解等问题,帮助你在实际项目中写出既灵活又可维护的数据访问层代码。

Spring Data JPA 最大的卖点之一,就是它能根据方法名自动生成查询语句,比如 findByUserNameAndStatus 这种命名约定查询,简单场景下非常好用。但一旦遇到多表关联、子查询、聚合统计或者数据库方言相关的函数调用,方法命名就会变得冗长甚至无能为力。这时候 @Query 注解就派上用场了,它允许你直接在 Repository 接口方法上书写 JPQL 或者原生 SQL,把查询的控制权交还给开发者。

Spring Data JPA 中如何使用 @Query 注解编写原生 SQL 和 JPQL 复杂查询

@Query 注解基础与 JPQL 写法

@Query 注解的核心属性其实很少,常用的就三个:value 用于书写查询语句,nativeQuery 标记是否为原生 SQL,name 用于引用预先定义在实体上的命名查询。先看一个最基础的 JPQL 例子:

public interface UserRepository extends JpaRepository<User, Long> {

    // JPQL 查询,注意操作的是实体和实体属性,而不是表名和列名
    @Query("select u from User u where u.email = :email")
    User findByEmail(@Param("email") String email);

    // 模糊查询 + 排序
    @Query("select u from User u where u.userName like %:keyword% order by u.createTime desc")
    List<User> searchByKeyword(@Param("keyword") String keyword);
}

这里要特别强调一点,JPQL 是面向对象的查询语言,它操作的是实体类和实体属性。from User 中的 User 是 @Entity 注解标注的类名,u.createTime 是实体里的字段名,而不是数据库表名和列名。初学者最常犯的错误就是把表名写进 JPQL,结果启动时报“无法识别的标识符”。

参数绑定推荐使用命名参数(:param)配合 @Param 注解,而不是位置参数(?1?2)。位置参数在后续维护中一旦调整顺序就会悄悄引入 Bug,而命名参数的可读性和重构安全性都好得多。另外,JPQL 中写 like 模糊匹配时,可以直接把百分号写在语句里(如上例的 %:keyword%),Spring 会自动处理参数拼接。

原生 SQL 的使用场景与写法

当查询涉及数据库特有函数(如 MySQL 的 date_format、Oracle 的分析函数)、复杂的多表 join、或者需要绕开 JPA 的实体映射直接操作部分字段时,就需要把 nativeQuery 设为 true:

public interface OrderRepository extends JpaRepository<Order, Long> {

    // 原生 SQL,直接写表名和列名
    @Query(value = "select o.* from t_order o " +
                   "join t_user u on o.user_id = u.id " +
                   "where u.status = :status and o.amount > :amount",
           nativeQuery = true)
    List<Order> findBigOrdersByUserStatus(@Param("status") Integer status,
                                          @Param("amount") BigDecimal amount);
}

原生 SQL 的返回值处理比 JPQL 灵活一些。如果查询的就是实体的全部字段,可以直接返回实体列表;如果只查部分字段,可以返回 List<Map<String, Object>>、Object 数组,或者通过接口投影返回 DTO。其中接口投影是比较推荐的方式:

public interface OrderStatsProjection {
    Long getUserId();
    BigDecimal getTotalAmount();
    Integer getOrderCount();
}

@Query(value = "select user_id as userId, sum(amount) as totalAmount, " +
               "count(*) as orderCount from t_order group by user_id",
       nativeQuery = true)
List<OrderStatsProjection> statsGroupByUser();

注意别名列要和接口方法名对应上(Spring Data 会自动做驼峰匹配,但显式写别名最稳妥)。原生 SQL 有一个知名的坑:分页时 Pageable 是支持的,但 Sort 中的属性会被直接拼接到 SQL 的 order by 后面,属性名必须写数据库列名而不是实体字段名,否则会直接报错。更严重的是,如果原生 SQL 是分页查询且使用了 Page 返回值,Spring 会额外生成一条 count 查询来统计总数,这条 count SQL 有时会把原 SQL 包一层 select count(*) from (...),个别复杂 SQL 会出现语法问题,此时建议自定义 countQuery 属性来规避。

更新删除与动态查询进阶技巧

写 DML 语句(update、delete)时,必须额外加上 @Modifying 注解,否则框架会把它当成 select 解析,启动即报错。同时别忘了在调用方开启事务,常见做法是在 Service 层方法上加 @Transactional

public interface UserRepository extends JpaRepository<User, Long> {

    @Modifying
    @Query("update User u set u.status = :status where u.lastLoginTime < :deadline")
    int batchUpdateStatus(@Param("status") Integer status,
                          @Param("deadline") LocalDateTime deadline);
}

@Service
public class UserService {
    @Autowired
    private UserRepository userRepository;

    @Transactional
    public int freezeInactiveUsers() {
        return userRepository.batchUpdateStatus(0,
                LocalDateTime.now().minusMonths(6));
    }
}

这里要注意,@Modifying 默认不会清空一级缓存,如果更新后紧接着查询同实体,可能读到旧数据。解决办法是设置 @Modifying(clearAutomatically = true, flushAutomatically = true),让框架在更新后清空并刷新持久化上下文。另外,JPQL 的 update 只能更新简单属性,不能操作关联集合,这类需求老老实实用实体加载后再修改更可靠。

对于排序、分页这类动态需求,@Query 同样可以和 Pageable 无缝整合。JPQL 中不需要写 order by,直接在方法上加 Pageable 参数,框架会自动追加;还可以用 SpEL 表达式 ?#{#pageable} 处理一些特殊场景。排序方向和字段在运行时由前端传入,拼接进 Pageable 是安全的,因为框架最终会解析成表达式对象而不是字符串拼接,不存在 SQL 注入风险。

最后给一个选型建议:单表或简单关联查询优先用方法命名约定;涉及多表字段聚合、报表统计时用 JPQL 加接口投影;只有碰到数据库方言函数、存储过程调用或者需要精确控制执行计划的场景,才切换到原生 SQL。把这三层工具用对位置,数据访问层的代码会清爽很多。

@Query注解JPQL原生SQL修改时间:2026-09-07 01:44:33

免责声明:已尽一切努力确保本网站所含信息的准确性。网站作品多为原创整理与精心创作,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们进行处理Email:chomcom@qq.com。
引用或转载本作品时,请注明当前出处:https://www.ipipp.com/html/20260907/51896.html,基于非商业用途的前提下,欢迎转载或二创本作品。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。