MyBatis中IN语句必须用foreach遍历集合,不可直接#{ids}传List;collection需与@Param一致,空集合须用if判断兜底,且foreach内必须全用#{item}防注入。

IN语句必须用foreach,不能用#{}直接传集合
MyBatis的PreparedStatement不支持把整个List/Array当一个参数绑定到IN (?)里——JDBC规范只允许单个值占位。硬写WHERE id IN #{ids}会报错或查不到数据,不是语法问题,而是底层机制限制。
常见错误现象:BindingException: Parameter 'ids' not found,或日志里显示IN ()空括号,查询返回空结果但无明显异常。
-
collection必须是可迭代对象(List、Set、Long[]等),不能是逗号分隔的String - 传
String如"1,2,3",得在Java层先split(",")转成List再传 - 接口方法用了
@Param("userIds"),XML中collection就得写"userIds",不是"list"或"ids"
foreach体内必须全用#{item},禁用${item}
foreach只是生成(?, ?, ?)结构的语法糖,它本身不防注入。安全靠的是每个#{id}触发预编译绑定——数据库收到的是三个独立参数值,不是拼接字符串。
只要混进一处${id},整条SQL就失去防护。比如#{id} OR name = ${name},后半句直接拼接,攻击者输a' OR '1'='1就能绕过。
- 模糊搜索也要每个
#{kw}走预编译,再用CONCAT('%', #{kw}, '%')或Java层拼好传入 -
${}在foreach里唯一合法场景是动态表名/列名,但必须配合白名单校验,和防注入无关 -
#{item.name}这种写法要确保item类型有name字段,否则OGNL报错,不是注入问题
空集合或null必须用兜底
foreach遇到空集合或null时默认不输出任何内容,会导致SQL语法断裂。例如WHERE id IN后面直接跟AND status = 1,抛SQLSyntaxErrorException。
这不是注入风险,但会让查询崩掉,而且日志里往往只显示“SQL error”,容易误判为数据问题而非逻辑缺失。
- 必须写
包裹整个... foreach块 - 别用
ids.size() > 0,某些MyBatis版本对空集合调用size()可能NPE -
能自动去首段AND,但它不处理IN后无内容的问题,不能替代空判断
Map参数遍历时collection和index要配对使用
传Map做条件时,collection填Map变量名,index接收key,item接收value。这是唯一能安全遍历键值对的方式。
比如按角色ID列表查用户,collection="roleMap",index="roleKey",item="roleId",然后#{roleId}才是安全值。
- 不能把Map当List用:写
collection="roleMap"却在循环里用#{roleMap.name},OGNL找不到属性 - 如果想用key做字段名(如
AND ${index} = #{item}),${index}必须来自白名单校验,不能直接信任前端传来的key - Map的key如果是数字或特殊字符,OGNL解析可能失败,建议统一用合法标识符命名
实际最易被忽略的点:空集合判断和collection命名一致性。前者让SQL崩得无声无息,后者让错误出现在运行时而非编译期,排查成本高。











