setparameter()只能绑定值而非sql结构,如order by ?会转为order by 'xxx'报错;列名、表名等结构须白名单校验后硬编码,in子句需用setparameterlist()避免参数绑定失效。

原生SQL里用 setParameter() 为什么还是报错?
因为 setParameter() 只能绑定「值」,不能绑定「结构」。比如你在原生 SQL 中写 "ORDER BY ?",然后调用 query.setParameter(0, "userName"),Hibernate 不会把它当列名插入,而是当成字符串字面量加引号处理,最终生成类似 ORDER BY 'userName' 的非法语法。
常见错误现象:SQLSyntaxErrorException、"Unknown column 'xxx'" 或直接返回空结果但无报错。
- 真正能安全传入的只有 WHERE 条件值、LIMIT 数字、IN 列表元素等纯数据项
- 列名、表名、排序方向(ASC/DESC)、GROUP BY 字段 —— 这些都属于 SQL 结构,必须白名单校验后硬编码
- 如果字段来自配置或枚举,建议用
switch或Map.of("name", "user_name", "age", "user_age")映射,杜绝字符串拼接
IN 子句怎么写才不漏掉参数绑定?
很多人以为 setParameter("ids", Arrays.asList(1L, 2L, 3L)) 就完事了,其实 Hibernate 会把整个 List 当作一个参数传进去,导致生成 WHERE id = ?(只有一个问号),而不是 WHERE id IN (?, ?, ?)。
正确做法是用 setParameterList():
String sql = "SELECT * FROM user WHERE id IN :ids";
Query query = session.createNativeQuery(sql);
query.setParameterList("ids", Arrays.asList(1L, 2L, 3L));
- 注意:
setParameterList()是 Hibernate 特有方法,JPA 标准TypedQuery不支持,必须用 Hibernate 原生Query - 列表为空时,Hibernate 6 默认抛出异常;如需兼容空列表,得提前判断并改写 SQL(例如用
1=0替代IN ()) - 不要用字符串拼接生成
IN (1,2,3)—— 这是高危操作,哪怕你“过滤了单引号”也不保险
动态表名或字段名真没法参数化?那怎么防?
是的,SQL 结构无法参数化。但别 resort 到 StringEscapeUtils.escapeSql() —— 它只转义单引号,对 UNION SELECT、注释符 --、分号 ; 都无效,且 Hibernate 6 已弃用该工具类。
可行方案只有两个:
- 白名单硬编码:所有可能的字段名/表名收口到枚举或静态 final Map,输入值仅用于查表映射,不参与拼接
- Criteria API 重构:如果业务允许,把原生 SQL 改成 JPA Criteria 查询,天然规避结构注入(但代价是可读性下降、复杂查询难写)
例如用户传 sortField=userName,你不能做 "ORDER BY " + sortField,而应该:
String column = switch (sortField) {
case "name" -> "user_name";
case "age" -> "user_age";
default -> "id";
};
sql += " ORDER BY " + column + " " + (asc ? "ASC" : "DESC");
为什么有些项目还在用字符串拼接原生 SQL?
因为开发时图快,测试时没覆盖边界输入,上线后靠 WAF 或数据库权限卡住攻击面 —— 这不是防御,是赌运气。
真正容易被忽略的点在于:原生 SQL 的「安全边界」比 HQL 更窄。HQL 至少能拦住大部分结构注入(比如 ORDER BY :field 会编译失败),而原生 SQL 允许你写任何合法语法,只要 JDBC 驱动认得,Hibernate 就照发。
所以只要用了 createNativeQuery(),就得默认所有拼接点都是潜在注入点,每个变量都要回答:它是值?还是结构?前者走 setParameter 系列,后者必须过白名单。










