【今天下午正准备摸鱼,测试突然在群里@我:“批量查询接口又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。


排查脑回路

  1. 先怀疑是 Mapper 多参数没加@Param,导致 XML 里取不到名字。MyBatis 对未加注解的多参会默认成param1/param2,而我 XML 里写的是ids/status,这就对不上了。
  2. 修完后又报IN (),盯日志发现有时上游 Redis 返回空集合,我们还硬拼了WHERE id IN (),MySQL 直接给你一巴掌。
  3. 最后两件事都搞定:加@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 ()把你一锅端。
  • 单测加上“空集合”这种边界,能省一下午的排查时间。】
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐