MyBatis 动态 SQL 中大量 OR 导致 JSQLParser 栈溢出问题排查

复制代码
## 问题现象

线上 WMS 成衣入库回传接口报错:

```text
StackOverflowError
net.sf.jsqlparser.expression.BinaryExpression.toString

接口日志看起来像是执行到 DTO 校验附近后中断:

复制代码
log.info("saveWmsTailoring req:{}", JSONObject.toJSONString(tailoringQuantityDto));
tailoringQuantityDto.validated();

一开始容易误判为参数校验或 DTO 序列化问题,但真正原因在后续 SQL 查询。

问题原因

接口后续查询入库计划条码明细时,MyBatis XML 中使用了 <foreach separator="OR"> 拼接多组条件:

复制代码
AND (
    (cloth_code = ? AND is_qualified = ?)
    OR (cloth_code = ? AND is_qualified = ?)
    OR (cloth_code = ? AND is_qualified = ?)
)

当 WMS 回传明细数量很多时,SQL 会生成非常长的 OR 表达式。

项目中启用了租户拦截器,拦截器内部会通过 JSQLParser 解析 SQL。大量 OR 会形成很深的 BinaryExpression 表达式树,最终在 toString() 时触发栈溢出。

所以根因不是业务校验,而是:

复制代码
超长 OR SQL -> JSQLParser 解析表达式树过深 -> StackOverflowError

解决方案

不改租户拦截器,只做 SQL 最小改动。

原 SQL:

复制代码
AND (
<foreach collection="clothCodes" item="item" separator="OR">
    (epdc.cloth_code = #{item.clothCode}
     AND epdc.is_qualified = #{item.isQualified})
</foreach>
)

改为 MySQL 支持的 tuple IN:

复制代码
AND (epdc.cloth_code, epdc.is_qualified) IN
<foreach collection="clothCodes" item="item" open="(" separator="," close=")">
    (#{item.clothCode}, #{item.isQualified})
</foreach>

修改后生成的 SQL 类似:

复制代码
AND (cloth_code, is_qualified) IN ((?, ?), (?, ?), (?, ?))

这样仍然是按 (cloth_code, is_qualified) 组合匹配,业务语义不变,但避免了超长 OR 表达式树。

验证结果

修改后执行模块编译:

复制代码
mvn -pl fz-scm-java-web -am compile

结果:

复制代码
BUILD SUCCESS

排查同类问题

后续如果再遇到类似问题,可以优先搜索 MyBatis XML:

复制代码
<foreach ... separator="or">

尤其要重点关注循环体里包含多个 AND 条件的写法:

复制代码
(field1 = ? AND field2 = ?) OR ...

这类 SQL 如果入参列表较大,就有触发 JSQLParser 栈溢出的风险。

总结

这类问题的关键点是:日志表象可能误导排查方向,真正异常不一定发生在最后一行业务日志附近。

遇到 JSQLParser + StackOverflowError + BinaryExpression.toString 时,应优先怀疑 SQL 中存在大量 OR 拼接。对于多字段等值组合匹配,MySQL 下可以优先考虑使用 tuple IN 替代 OR 链。

```

17:10