JdbcTemplate和MyBatis动态SQL防注入关键在写法:参数绑定(?或#{})仅防值注入,字段名、表名、排序等结构需白名单校验;IN查询用NamedParameterJdbcTemplate;LIKE通配符由Java层拼接。

JdbcTemplate 和 MyBatis 的动态 SQL 本身不防注入,关键在写法——用参数绑定的才安全,拼字符串的全都不行。
用 JdbcTemplate 写动态 WHERE 条件时,别拼 SQL 字符串
常见错误是把条件逻辑硬塞进 SQL 字符串里:
- ❌
"SELECT * FROM user WHERE 1=1" + (name != null ? " AND name = '" + name + "'" : "")—— 单引号、OR 1=1全部直通数据库 - ✅ 正确做法:先定义带固定
?占位符的模板,再动态构造参数列表 - 例如:
"SELECT * FROM user WHERE status = ?" + (minAge != null ? " AND age > ?" : ""),然后根据条件数传new Object[]{status}或new Object[]{status, minAge} - ⚠️ 注意:JDBC 驱动只对
?位置做类型绑定,不会解析 SQL 结构。多一个?少一个都会抛org.springframework.dao.InvalidDataAccessResourceUsageException
MyBatis 动态排序/分页必须白名单校验字段名
#{} 只能防「值」注入,对 ORDER BY、GROUP BY、表名、列名完全无效:
- ❌
ORDER BY ${sortField}—— 攻击者传sortField=id; DROP TABLE user;就执行了 - ✅ 必须提前定义合法字段集:
private static final Set<string> ALLOWED_SORT_FIELDS = Set.of("id", "name", "created_time");</string> - 在 Service 层校验:
if (!ALLOWED_SORT_FIELDS.contains(sortField)) { throw new IllegalArgumentException("Invalid sort field"); } - MyBatis XML 中仍用
${sortField},但前提是已过白名单——这是唯一能接受字符串替换的场景
IN 查询必须换 NamedParameterJdbcTemplate
原生 JdbcTemplate 不支持变长 IN 列表,硬拼问号会出错:
- ❌
"WHERE id IN (" + String.join(",", Collections.nCopies(ids.size(), "?")) + ")"—— 看似聪明,实则绕过 PreparedStatement 绑定,且易被注入(如果 ids 来自用户输入) - ✅ 用
NamedParameterJdbcTemplate:"SELECT * FROM user WHERE id IN (:ids)",再传MapSqlParameterSource("ids", ids) - 底层驱动自动展开
:ids为对应数量的?并一一绑定,不是字符串替换 - ⚠️ 注意:
ids必须是List或数组,不能是逗号分隔字符串;空集合会报错,需提前判空
模糊查询 LIKE 的通配符要由 Java 层拼,别丢给 SQL
想查 %keyword%,很多人直接写 WHERE name LIKE '%?%',这会导致语法错误:
- ❌
"WHERE name LIKE '%?%'"——?不会被当通配符处理,实际查的是字面量'%?%' - ✅ 正确:Java 层加通配符:
String likeValue = "%" + keyword + "%";,SQL 写成"WHERE name LIKE ?",参数传likeValue - MyBatis 同理:
WHERE name LIKE #{keyword},调用时传"%" + keyword + "%" - ⚠️ 如果 keyword 本身含
%或_,且业务需要精确匹配,得额外用ESCAPE指定转义符,比如LIKE ? ESCAPE '',并把 keyword 中的%替换成\%
真正容易被忽略的是:动态 SQL 的「结构安全」和「值安全」是两回事。你可能 100% 用了 #{} / ?,但只要在 SQL 字符串里拼了任何用户可控的字段名、表名、排序方向,防护就形同虚设。


















