IN查询必须用#{}配合,禁用${}拼接;空集合需用判空防报错或全表扫描;不同传参方式(@Param/Map/DTO)的判空表达式写法各异;复杂动态SQL推荐替代。

IN查询必须用#{},不能用${}
直接拼接字符串进IN子句(比如${ids})等于主动打开SQL注入大门。攻击者传入1,2,3) OR 1=1 --,就会让SQL变成WHERE id IN (1,2,3) OR 1=1 --),绕过条件查全表。MyBatis不校验${}里的内容,它只是原样替换,没有任何类型检查或转义。
正确做法是:所有值都走#{id},由<foreach></foreach>生成占位符序列,最终交给JDBC预编译处理。数据库看到的是WHERE id IN (?, ?, ?),参数值单独传入——恶意输入只会被当字符串值匹配,无法改变SQL结构。
<foreach></foreach>空集合不拦截,必须配<if></if>判断
这是高频翻车点:<foreach collection="list" item="id" open="(" close=")" separator=",">#{id}</foreach>遇到空List或null时,会渲染成WHERE id IN (),MySQL直接报1064错误;若外层没<if></if>包裹,还可能退化为无WHERE的全表扫描。
必须显式加判空逻辑:
-
<if test="ids != null and ids.size() > 0"></if>—— 适用于@Param("ids") List<long> ids</long>传参 -
<if test="params.ids != null and params.ids.size() > 0"></if>—— 适用于Map传参,注意key名要匹配 - DTO场景下,
query.getIds()返回null会触发NPE,务必确保getter返回空集合而非null
不同传参方式下size()检查写法有差异
MyBatis对Map和@Param的表达式解析规则不同,写错就失效:
- 用
@Param("ids"):test="ids != null and ids.size() > 0",ids直接可用 - 用
Map<string object></string>:test="ids != null and ids.size() > 0"会报BindingException,必须写test="paramMap.ids != null and paramMap.ids.size() > 0"或把Map整体设为@Param("params")再用params.ids - 用DTO对象:
test="query.ids != null and query.ids.size() > 0",前提是query是传入的属性名,且getIds()不返回null
别依赖IDE自动补全,运行时看BoundSql.getSql()输出最准。
复杂场景下<trim></trim>比<where></where>更可控
当IN条件和其他动态条件混用,且IN部分需要独立控制前后缀时,<where></where>的自动去首AND行为反而碍事。例如:
<where>
<if test="name != null">AND name = #{name}</if>
<if test="ids != null and ids.size() > 0">AND id IN <foreach ...></foreach></if>
</where>
如果ids为空、name也不传,<where></where>会生成空WHERE子句,导致语法错误。此时改用<trim prefix="WHERE" prefixoverrides="AND"></trim>,能精确控制是否加WHERE以及是否删掉开头的AND。
真正难的不是写对语法,而是每次加新条件时,都要重新检查空集合路径是否被覆盖——尤其在多人协作的Mapper里,一个没加<if></if>的<foreach></foreach>就能让整条SQL崩掉。

















