nativequery = true 本身安全,危险源于字符串拼接、动态表名列名或绕过参数绑定;正确使用占位符绑定、避免spel、in子句需手动处理、@modifying需显式事务、结果映射须显式配置。

@Query 配合 nativeQuery = true 本身不危险,危险的是你让它执行什么——只要不用字符串拼接、不放行动态表名/列名、不绕过参数绑定,原生 SQL 就和派生方法一样安全。
为什么 nativeQuery = true 不等于自动中招
Spring Data JPA 对 @Query(nativeQuery = true) 的处理逻辑很明确:它跳过 Hibernate 的 JPQL 解析层,把 SQL 字符串原样交给 JDBC 执行。但“原样交”不等于“不设防”——JDBC 仍会识别 :param 或 ?1 这类占位符,并走 PreparedStatement.setString() 绑定流程。
- ✅ 正确写法:
@Query(value = "SELECT * FROM user WHERE email = :email", nativeQuery = true)→ 参数进绑定槽,SQL 结构固定 - ❌ 错误写法:
@Query(value = "SELECT * FROM user WHERE email = '" + email + "'", nativeQuery = true)→ 字符串拼接,直接破防 - ⚠️ 隐蔽陷阱:
@Query(value = "SELECT * FROM :table WHERE id = ?1", nativeQuery = true)→ 表名无法参数化,运行时报错或查不到数据,不是安全用法
IN 子句怎么写才不报错又防注入
原生 SQL 中 WHERE id IN :ids 会直接抛 ParameterizedQueryException,因为 JDBC 不支持数组参数在原生语句里自动展开。Hibernate 只在 JPQL 里做这个事,原生层不接管。
- 必须手动确定长度并写死占位符个数,例如:
WHERE id IN (?1, ?2, ?3) - 对应方法参数得是三个独立值(
Long a, Long b, Long c),不能传List<long></long> - 若长度不确定,别硬撑——改用
JdbcTemplate拼IN字符串(需白名单校验输入长度)或退回到Specification+ 多次OR - 别用 SpEL:
@Query(value = "SELECT * FROM user WHERE id IN #{#ids}", nativeQuery = true)→#{}是运行时字符串替换,等同于拼接
@Modifying + nativeQuery = true 的事务陷阱
原生 UPDATE / DELETE 必须加 @Modifying,否则 Spring 会静默忽略或抛 InvalidDataAccessResourceUsageException;但加了还不够,事务得由调用方显式控制。
-
@Modifying方法默认不开启事务,即使 Repository 接口加了@Transactional也无效 - 必须在 Service 层方法上加
@Transactional,且传播行为为REQUIRED(默认) - 返回类型建议用
int,检查影响行数,避免“以为更新成功实则没匹配到记录” - 慎用
@Modifying(clearAutomatically = true):它会清空一级缓存,可能引发后续查询查不到刚更新的实体
结果映射别踩 DTO 构造器的坑
原生 SQL 返回字段名是数据库列名(如 IS_ACTIVE),而 JPQL 投影构造器(new MyDto(col))只在 JPQL 里生效;nativeQuery = true 下写 new MyDto(IS_ACTIVE) 会直接报错。
- 正确做法一:返回
List<object></object>,在 Service 层手动 new DTO - 正确做法二:用
@SqlResultSetMapping+@ConstructorResult显式绑定列名到构造参数 - 错误示范:
@Query(value = "SELECT IS_ACTIVE FROM user", nativeQuery = true) List<mydto></mydto>→ 没声明resultClass或映射,JPA 尝试按实体映射,失败 - 如果 DTO 字段名和数据库列名一致,可加
resultClass = MyDto.class,但前提是列顺序、类型、数量完全匹配
复杂点在于:原生 SQL 的安全边界全靠开发者手控——没有 Hibernate 在中间兜底,每一处字符串拼接、每一个动态字段、每一次忘记 @Modifying,都是裸奔。 它不像派生方法那样“默认安全”,而是“默认中立”,信不信它,取决于你怎么写那条 SQL。










