@Query(nativeQuery = true)默认不防SQL注入,因原生SQL字符串由开发者拼写,仅占位符参数化;表名、列名、排序方向等无法参数化,须白名单校验;IN子句需固定占位符或改用JdbcTemplate;动态条件应使用JpaSpecificationExecutor而非字符串拼接。

为什么@Query(nativeQuery = true)默认不防注入
原生 SQL 绕过 Hibernate 的 JPQL 解析层,直接交由 JDBC 执行。它只保证 :param 或 ?1 这类占位符走预编译,但整个 SQL 字符串由开发者拼写——一旦混入用户输入,就等于把 SQL 结构暴露给外部控制。
常见破防点:
-
@Query(value = "SELECT * FROM user WHERE name = '" + name + "'", nativeQuery = true):字符串拼接直接触发注入 -
@Query(value = "SELECT * FROM user WHERE name = '#{#name}'", nativeQuery = true):SpEL 表达式在运行时做字符串替换,等同于拼接 -
@Query(value = "SELECT * FROM :table WHERE id = ?1", nativeQuery = true):表名、列名、排序方向(ORDER BY :sortField)无法参数化,JPA 会报错或查不到数据
IN 子句怎么安全传集合参数
JPA 原生 SQL 不支持 WHERE id IN :ids 这种写法——它不会自动展开为多个 ? 占位符,而是抛出 org.hibernate.QueryException: Named parameter [ids] not set 或类似错误。
正确做法只有两种:
- 提前确定集合长度,手动写出固定数量的占位符,例如
WHERE id IN (?1, ?2, ?3),再按顺序传三个参数 - 改用
JdbcTemplate+PreparedStatementCreator,利用new SqlParameterValue()或NamedParameterJdbcTemplate的query()方法,后者支持WHERE id IN (:ids)并自动展开
别试图用 StringBuilder 拼出带 N 个 ? 的 SQL 再塞进 @Query:这仍是运行时字符串拼接,且破坏了注解的编译期静态性。
动态条件该用 Specification 而不是拼 SQL
业务中“按需加 WHERE”的需求很常见,但有人会写:StringBuilder sql = new StringBuilder("SELECT * FROM user"); if (name != null) sql.append(" WHERE name = '").append(name).append("'"); ——这是最典型的自毁行为。
根本原因:@Query 是编译期固定的,无法运行时改变结构;所谓“动态”只能靠外部拼接字符串,而拼接即注入温床。
替代方案:
- 让 Repository 接口继承
JpaSpecificationExecutor<User> - 每个条件封装为独立谓词,如
cb.like(root.get("name"), "%" + name + "%"),其中%是写死在代码里的字面量,name是纯参数值 - 用
Specifications.where().and().or()安全组合嵌套条件,最终仍生成预编译语句
模糊查询和排序字段如何处理才不翻车
模糊查询常写成 WHERE name LIKE '%'+?+'%',这是错的——字符串拼接发生在 SQL 层,不是参数绑定。
正确方式:
- 在 Java 层拼好通配符:
String pattern = "%" + userInput + "%",再传给@Param("pattern"),SQL 中写WHERE name LIKE :pattern - 排序字段(如
ORDER BY :sortField)不能参数化,必须白名单校验:if (!List.of("name", "created_time", "status").contains(sortField)) throw new IllegalArgumentException(); - 排序方向(
ASC/DESC)同理,禁止直接拼接,应转为布尔开关或枚举映射
真正难的不是写对一行代码,而是意识到:所有需要动态决定 SQL 结构的地方(表名、字段名、操作符、排序方向),都必须在参数绑定之外另设校验机制——因为 PreparedStatement 根本不管这些。

















