使用@Query执行SQL本身安全,但混用命名参数与索引参数会导致绑定失败;nativeQuery=true时需手动对齐列名与返回类型,动态标识符不可参数化,@Modifying操作必须配@Transaction。

直接用 @Query 执行 SQL 本身不危险,危险的是你怎么传参、怎么构造字符串、以及是否绕过了预编译机制。
命名参数和索引参数必须二选一,不能混用
两种方式都走 PreparedStatement,但混用会导致参数错位或绑定失败:
-
@Query("SELECT * FROM user WHERE status = ?1 AND dept_id = ?2"):按方法参数顺序绑定,?1对应第一个参数,?2对应第二个 -
@Query("SELECT * FROM user WHERE status = :status AND dept_id = :deptId")+@Param("status") Integer status:语义清晰,支持同名多次引用,推荐用于多参数或复用场景 - 错误写法:
@Query("WHERE status = ?1 AND dept_id = :deptId")——Hibernate 不识别这种混合占位符,运行时报IllegalArgumentException: Positional parameter does not exist
nativeQuery = true 时字段映射和返回类型要手动对齐
启用原生 SQL 后,JPA 不再自动做属性名到列名的驼峰/下划线转换,所有字段名必须显式匹配或别名化:
- 查完整实体时,列名必须与实体属性名完全一致(区分大小写),否则对应字段为
null - 推荐写法:
SELECT id AS id, user_name AS userName, created_at AS createdAt FROM user - 只查部分字段时,别返回
List<user></user>,改用List<object></object>或自定义 DTO,再手动封装 - 错误示例:
@Query(value = "SELECT id, user_name FROM user", nativeQuery = true)返回List<user></user>→userName字段永远是null
动态排序、表名、列名不能靠 :param 绑定
:param 只能在值上下文中安全使用(WHERE、HAVING、ORDER BY 的值部分),不能用于标识符:
-
@Query("SELECT u FROM User u ORDER BY u.:sortField")—— 编译报错,JPA 不支持 -
@Query(value = "SELECT * FROM :table", nativeQuery = true)—— 运行时报错或查不到数据,:table被当字面量处理 - 真正需要动态列名或排序方向时,必须白名单校验后硬拼:
if (!Set.of("id", "name", "created_at").contains(sortField)) throw new IllegalArgumentException(); - 排序方向(
ASC/DESC)同样不能参数化,只能用if-else或三元表达式控制 SQL 字符串拼接(注意:仅限白名单内字段)
@Modifying 更新操作必须配 @Transactional
任何带 @Modifying 的 @Query(UPDATE/DELETE/INSERT)都会抛 TransactionRequiredException,除非事务已开启:
- 最稳妥做法:在 service 方法上加
@Transactional,不要只加在 repository 层 - 如果方法自己加
@Transactional,注意传播行为,默认REQUIRED即可 - 漏掉事务的典型错误信息:
Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException - 如需同步清除一级缓存(避免后续
findById拿到旧值),加clearAutomatically = true
最容易被忽略的点是:有人在 service 层用 String.format 或 StringBuilder 拼完 SQL 再丢给 JdbcTemplate,这种代码既不报错也不留痕,却彻底绕过所有 JPA 安全机制。防注入不是写对一个 @Query 就完事,而是整条调用链都不能出现字符串插值。

















