mybatis的foreach标签本身不防sql注入,真正起防护作用的是循环体内使用的#{item}预编译机制;若混用${item}拼接字符串,即使仅一处也会导致整条sql失去防护,且空集合或collection名称不匹配将引发语法错误或bindingexception。

foreach标签本身不防注入,安全靠#{item}预编译
MyBatis 的 foreach 标签只是生成 (?, ?, ?) 这类占位符结构的语法糖,它不校验、不转义、不拦截输入。真正防注入的是你在循环体内写的 #{id} —— 它触发 PreparedStatement 对每个元素单独绑定,数据库只把值当数据处理。
常见翻车点:<foreach collection="ids" item="id">#{id} OR id = ${id}</foreach>,后半句 ${id} 直接拼字符串,哪怕只有一处,整条 SQL 就失去防护。
- 传入
List<string></string>做模糊关键词搜索,必须每个都走#{keyword},不能因为“是集合”就放松对单个元素的信任 -
CONCAT('%', #{kw}, '%')是安全的;CONCAT('%', ${kw}, '%')是高危的,哪怕kw来自foreach内部 - 如果业务需要动态字段名(如
ORDER BY ${sortField}),foreach无能为力,必须用白名单校验,和foreach安全机制无关
collection 和 item 必须严格匹配参数命名与类型
配错 collection 或 item 不会导致注入,但会让 SQL 生成失败或查出空结果,属于运行时错误,不是安全漏洞。
典型错误现象:BindingException: Parameter 'xxx' not found,或日志里出现 IN () 空括号。
- 接口方法是
UserMapper.selectByIds(@Param("userIds") List<long> ids)</long>→ XML 中collection="userIds",不是"list"或"ids" - 传入的是数组
Long[],没加@Param→collection="array",不是"list" -
item="uid",但内部写#{userId}→ OGNL 找不到变量,报错 - 遍历
List<string></string>却写#{uid.name}→ 运行时报ognl.MethodFailedException
空集合或 null 必须用 显式兜底
foreach 遇到 null 或空集合时默认不输出任何内容,这会导致 SQL 语法断裂,比如 WHERE id IN 后直接跟 AND status = 1,抛 SQLSyntaxErrorException。
这不是注入问题,但会让查询崩掉,而且容易被忽略——因为没报 BindingException,日志里也看不出明显异常。
- 必须写
<if test="ids != null and !ids.isEmpty()"></if>包裹整个foreach块 - 不要用
ids.size() > 0替代!ids.isEmpty(),OGNL 在某些版本对空集合调用size()可能抛 NPE -
<where></where>标签可自动去除首段 AND,但不能替代空集合判断逻辑
IN 子句只能用 foreach,不能用 #{ids} 直接传集合
JDBC 的 PreparedStatement 不支持把整个集合塞进一个 ? 占位符,WHERE id IN (#{ids}) 会尝试调用 setObject(1, list),要么报错,要么语义错误(比如把 List.toString() 当字符串匹配)。
foreach 是唯一正确解法:它在 MyBatis 解析阶段就展开成多个 ?,再由 JDBC 分别绑定每个元素。
- 正确:
<foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach> - 错误:
IN (#{ids})、IN #{ids}、IN (${ids}) - 排序字段、表名、列名等标识符无法用
foreach参数化,这些地方永远不能依赖用户输入,必须走白名单或配置驱动
collection 名称与 Java 层传参方式的隐式耦合——这两处不出错不报警,但一出就是线上故障。











