命名参数绑定的安全性取决于是否调用setparameter(),createquery()本身不防注入;动态表名/字段名须白名单校验;criteria api可彻底避免hql注入。

命名参数绑定不是“加个冒号就安全了”,关键在绑定动作是否真正发生、参数是否被当作数据而非代码解析。
为什么 createQuery("... :name ...") 单独写不防注入
Hibernate 的 createQuery() 只负责解析字符串并生成 Query 对象,它不检查你传进去的 HQL 是否含拼接、也不自动绑定任何值。如果你写了 createQuery("FROM User WHERE name = '" + input + "'"),哪怕用了 createQuery,照样中招——因为 Hibernate 拿到的是一个完整的、已拼好的字符串,没有“参数”可言。
真正起防护作用的是后续的 setParameter() 调用,它让 Hibernate 把值和 HQL 模板分离,在底层交由 JDBC 的 PreparedStatement 处理。
- ✅ 安全:
session.createQuery("FROM User WHERE name = :name").setParameter("name", userInput) - ❌ 危险:
session.createQuery("FROM User WHERE name = '" + userInput + "'")(没调用setParameter,也没用占位符) - ⚠️ 伪安全:
session.createQuery("FROM User WHERE name = :name")写对了,但忘了setParameter→ 查询会抛QueryParameterException,运行时失败,不是注入,但逻辑断了
setParameter() 和 setString()/setInteger() 的区别
Hibernate 5+ 已废弃带类型后缀的 setString("name", value) 等方法,统一推荐用泛型 setParameter()。它更健壮,且能自动推导类型(尤其配合 JPA 标准时)。
- ✅ 推荐:
query.setParameter("name", username)(自动识别 String 类型) - ✅ 显式指定类型:
query.setParameter("age", 25, StandardBasicTypes.INTEGER) - ❌ 过时写法:
query.setString("name", username)(Hibernate 5.2+ 已标记为 deprecated)
注意:setParameter() 的第一个参数必须严格匹配 HQL 中的命名(比如 :userName 就得传 "userName"),大小写敏感,拼错直接报错。
动态表名/字段名不能用参数化,必须白名单校验
HQL 和 SQL 都不支持把表名、字段名、排序方向(ASC/DESC)作为参数绑定。例如下面这段是非法的:
session.createQuery("FROM " + tableName + " WHERE id = :id") // ❌ tableName 无法参数化
这类动态结构必须靠白名单控制:
- 定义允许的表后缀:
Set<string> allowedTables = Set.of("user_2024", "user_2025");</string> - 校验输入:
if (!allowedTables.contains(tableSuffix)) throw new IllegalArgumentException("Invalid table"); - 再拼接:
"FROM user_" + tableSuffix + " WHERE id = :id"
别试图用 StringEscapeUtils.escapeSql() 或正则替换——HQL 不是 SQL,转义规则不同,且逃逸手法早已公开(如用 Unicode、注释符绕过),白名单才是唯一可靠手段。
Criteria API 是最省心的替代方案
如果你的查询逻辑不复杂,又想彻底避开字符串拼接风险,直接上 JPA Criteria API:
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<user> cq = cb.createQuery(User.class);
Root<user> root = cq.from(User.class);
cq.select(root).where(cb.equal(root.get("name"), userInput));
List<user> result = session.createQuery(cq).list();</user></user></user>
它全程类型安全、编译期可检、无字符串构造,天然免疫 HQL 注入。缺点是写法冗长,适合条件固定或可组合的场景;高频动态拼接(如搜索过滤器)仍建议用命名参数 + 白名单兜底。
最容易被忽略的一点:很多团队只改了 DAO 层的 createQuery,却忘了 Service 层可能还藏着 StringBuilder 拼 HQL 的逻辑,或者前端传来的排序字段(order by ${field})直接进了查询——这些地方同样要扫一遍,不能只盯住 createQuery 调用点。











