原生sql必须用参数占位符(?1或:param)而禁用字符串拼接,in子句需手动展开占位符,字段别名须与实体属性名严格一致,@modifying操作必须配合@transactional,动态排序和表名不可参数化。

原生 SQL 必须用参数占位符,不能拼字符串;否则哪怕只加一个单引号,就等于把数据库大门敞开。
nativeQuery = true 时必须用 ?1 或 :param,不能字符串拼接
启用 nativeQuery = true 后,JPA 不再解析 JPQL,而是直接把 SQL 交给 JDBC 执行。此时整个字符串由你全权负责——只要出现 "' + name + '" 或 String.format("WHERE name = '%s'", name),注入风险立刻生效。
- 错误写法:
@Query(value = "SELECT * FROM user WHERE name = '" + name + "'", nativeQuery = true)—— 编译期就报错,且运行时绝对禁止 - 正确写法(索引参数):
@Query(value = "SELECT * FROM user WHERE status = ?1 AND dept_id = ?2", nativeQuery = true) - 正确写法(具名参数):
@Query(value = "SELECT * FROM user WHERE status = :status AND dept_id = :deptId", nativeQuery = true),配合@Param("status")和@Param("deptId") - 注意:两种参数风格不能混用,
?1和:param在同一个查询里只能选其一
IN 子句不能直接传 List,得手动展开占位符
原生 SQL 不支持 WHERE id IN :ids 这种写法,Hibernate 不会帮你把 List 拆成 ?1, ?2, ?3。直接这么写会抛 IllegalArgumentException 或查不到数据。
- 错误写法:
@Query(value = "SELECT * FROM user WHERE id IN :ids", nativeQuery = true) - 安全替代方案一(固定长度):
@Query(value = "SELECT * FROM user WHERE id IN (?1, ?2, ?3)", nativeQuery = true),然后传入三个参数 - 安全替代方案二(换工具):改用
JdbcTemplate+new PreparedStatementCreator(),手动构建动态占位符数组 - 安全替代方案三(不硬刚):如果只是查数据,优先用
Specification+ JPQL,它天然支持root.get("id").in(ids)并自动展开
字段别名必须显式声明,否则实体属性映射为空
开启 nativeQuery = true 后,JPA 不再按实体属性名自动映射,而是严格按 SELECT 列名或下标匹配。数据库字段叫 user_name,但实体属性是 userName?不加别名就为 null。
- 危险写法:
SELECT id, user_name FROM user→ 返回的User对象中userName字段为null - 推荐写法:
SELECT id AS id, user_name AS userName FROM user—— 别名必须和实体属性名完全一致(含大小写) - 更稳妥做法:不返回完整实体,改用
@Query(..., nativeQuery = true)返回List<object></object>或自定义 DTO,自己封装 - 额外提醒:如果只查部分字段,强行返回
List<user></user>会导致未查字段被初始化为默认值(如 0、null),掩盖数据缺失问题
@Modifying + nativeQuery 必须配 @Transactional
任何带 @Modifying 的原生 SQL(UPDATE/DELETE/INSERT)都绕不过事务控制。没加 @Transactional 就执行,直接抛 TransactionRequiredException。
- 典型错误信息:
Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException - 必须满足之一:方法所在类已加
@Transactional(推荐加在 service 层),或该方法自己加注解 - 注意传播行为:默认
REQUIRED即可,除非有嵌套调用特殊需求 - 进阶选项:加
clearAutomatically = true可同步清除一级缓存,避免后续findById()拿到脏数据
最易被忽略的一点:动态排序字段(如 ORDER BY :sortField)和表名根本不能参数化,@Query 里写 FROM :table 是无效的。这类需求要么白名单校验后字符串拼接(慎之又慎),要么改用 JdbcTemplate 配合 SimpleJdbcCall 或视图封装。










