foreach标签本身安全,风险源于${}拼接或collection/item配置错误;需严格匹配参数名、保持item与#{ }变量一致,并用处理空集合。

foreach 标签本身不引入 SQL 注入风险,真正危险的是你把用户输入直接拼进 SQL 字符串(比如用 ${} 或字符串拼接),而不是用 #{} 占位。只要坚持用 #{item},foreach 是安全的 —— 它只是把预编译参数一个个塞进去,不是字符串替换。
但很多人踩坑,是因为 collection 和 item 配错了,导致 SQL 生成失败、空集合报错、甚至查出意外数据。下面说清楚怎么配、为什么这么配、哪里容易翻车。
collection 值必须严格匹配参数实际类型和传参方式
MyBatis 不会自动推断你要遍历哪个集合,它只认 collection 属性写的那个名字。这个值不是随便起的,它由接口方法签名 + 是否加 @Param 共同决定:
- 单个参数是
List<long></long>,没加@Param→collection="list" - 单个参数是
Long[],没加@Param→collection="array" - 参数是
@Param("ids") List<long> ids</long>→collection="ids" - 参数是
Map<string object> params</string>,里面放了params.put("userIds", list)→collection="userIds" - 参数是实体类
UserQuery query,其中query.getIds()返回List<long></long>→collection="ids"
写错的典型表现:org.apache.ibatis.binding.BindingException: Parameter 'xxx' not found,或者查询结果为空但日志里 SQL 显示 IN () —— 这说明 MyBatis 根本没找到集合,foreach 没执行。
item 名必须和 #{ } 中的变量名完全一致
item 是你在 foreach 内部给每个元素起的临时变量名,它只在当前标签作用域内有效。你写 item="id",就必须用 #{id};写 item="u",就得用 #{u}。大小写、下划线、拼写差一个字符都不行。
常见错误:
-
item="userId"却写成#{user_id}→ 报错:BindingException: Parameter 'user_id' not found - 遍历对象列表却用
#{item.id},但item是基本类型(如Long)→ 运行时报ognl.MethodFailedException - 用
item="user",但在#{user.name}中访问属性,而传入的其实是List<string></string>→ 同样抛 OGNL 异常
空集合或 null 时的 fallback 处理不能靠 foreach 自己
foreach 遇到空集合或 null 时默认什么都不输出,这会导致 SQL 语法错误,比如 WHERE id IN 后面直接跟 AND status = 1,变成非法语句。
正确做法是用 <if></if> 包一层:
<where>
<if test="ids != null and !ids.isEmpty()">
id IN <foreach collection="ids" item="id" open="(" separator="," close=")">#{id}</foreach>
</if>
<if test="status != null"> AND status = #{status}</if>
</where>
注意:!ids.isEmpty() 比 ids.size() > 0 更安全,避免 NPE;如果用的是数组,判断条件要改成 array != null and array.length > 0。
批量更新中用 CASE WHEN 时 collection 和 item 的嵌套陷阱
像这种结构:
UPDATE user SET score = CASE id
<foreach collection="users" item="u">
WHEN #{u.id} THEN #{u.score}
</foreach>
END WHERE id IN <foreach collection="users" item="u" open="(" separator="," close=")">#{u.id}</foreach>
有两个关键点容易漏:
- 两个
foreach必须共用同一个collection值(比如都用users),否则第二个foreach找不到集合,整个 SQL 少掉WHERE条件 -
item名在两个foreach中必须一致(比如都叫u),否则第二个#{u.id}会报错 - 如果
users是空集合,上面整段 SQL 会变成UPDATE user SET score = CASE id END WHERE id IN (),MySQL 会报错;必须加<if></if>判断
最稳妥的方式是把批量更新逻辑拆到 Service 层分批处理,而不是硬塞进一条 SQL —— 超过几百条时,foreach 生成的 SQL 可能超出 MySQL 的 max_allowed_packet 限制,且难以调试。

















