## 问题现象
线上 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