【MyBatis foreach把我坑惨了:一次BindingException + IN()空集合导致线上500】
·
【今天下午正准备摸鱼,测试突然在群里@我:“批量查询接口又500了,而且还挺随机。”我登上日志一眼瞟到:
org.apache.ibatis.binding.BindingException: Parameter 'ids' not found. Available parameters are [arg1, arg0, param1, param2]
at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:)
我心想这不就是经典的 MyBatis 坑么?结果刚修完一把,又来一条更离谱的:
com.mysql.cj.jdbc.exceptions.MysqlSyntaxErrorException: You have an error in your SQL syntax; near 'IN ()'
一个接口连环爆雷,把我整破防。
现场复盘
- 出问题的接口:按ID集合批量查订单详情,顺带按状态过滤。
- 现象:
- 有时直接抛
BindingException,提示找不到参数ids; - 修完后偶发
IN ()语法错误; - APM 里能看到DB请求被打断,失败率陡增。
- 有时直接抛
我用 Arthas trace 了一把:
trace com.xxx.order.dao.OrderMapper selectByIds -n 1
定位到就是这货在报错,参数是一个List<Long> + 一个Integer。
排查脑回路
- 先怀疑是 Mapper 多参数没加
@Param,导致 XML 里取不到名字。MyBatis 对未加注解的多参会默认成param1/param2,而我 XML 里写的是ids/status,这就对不上了。 - 修完后又报
IN (),盯日志发现有时上游 Redis 返回空集合,我们还硬拼了WHERE id IN (),MySQL 直接给你一巴掌。 - 最后两件事都搞定:加
@Param+对空集合短路返回,XML 里也做了空集合判断。
问题代码(反例)
接口定义:
public interface OrderMapper {
List<OrderDO> selectByIds(List<Long> ids, Integer status);
}
XML(反例):
<select id="selectByIds" resultType="com.xxx.order.dao.OrderDO">
SELECT id, user_id, status, amount
FROM t_order
WHERE status = #{status}
AND id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
- 多参数没
@Param,XML 里写collection="ids"根本取不到。 - 上游传空集合时,
IN ()语法错误直接起飞。
修复方案
核心两步:给多参数命名;空集合直接短路,不执行SQL。
1) 接口加 @Param,名字和XML对齐
public interface OrderMapper {
List<OrderDO> selectByIds(@Param("ids") List<Long> ids,
@Param("status") Integer status);
}
2) XML 动态判断,避免 IN ()
<select id="selectByIds" resultType="com.xxx.order.dao.OrderDO">
SELECT id, user_id, status, amount
FROM t_order
<where>
<if test="status != null">
AND status = #{status}
</if>
<if test="ids != null and ids.size() > 0">
AND id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</if>
</where>
</select>
注意:ids.size() > 0里是size()不是size,少了括号就等着挨坑。
3) 业务层兜底:空集合不查直接返回
public List<OrderDTO> batchDetail(List<Long> ids, Integer status) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
return orderMapper.selectByIds(ids, status).stream()
.map(this::toDTO)
.collect(Collectors.toList());
}
补充排雷点
- 多参数一定用
@Param明确命名,不要赌param1/arg0这种默认行为。 foreach的collection支持:list、array、map,需要和方法参数命名一致;或者你只传一个集合参数,XML 用collection="list"也行。- 真要允许空集合,也可以改造SQL:当集合为空时拼接
AND 1=0,语义更清晰(返回空集),但我更倾向在业务层短路。
<if test="ids == null or ids.size() == 0">
AND 1 = 0
</if>
验证
- 单测覆盖:空集合、单元素、上百元素混合,全部通过;
- 压测半小时,接口无500,DB无语法错误,RT恢复稳态;
- 线上再未出现
BindingException和IN ()相关告警。
踩坑总结
- MyBatis 多参不加
@Param迟早挨打,XML/接口命名一定对齐。 foreach千万别忘了空集合分支,别让IN ()把你一锅端。- 单测加上“空集合”这种边界,能省一下午的排查时间。】
更多推荐


所有评论(0)