在使用 MyBatis 开发过程中,有些问题不会直接抛异常,却会让人花大量时间定位和调试。本文结合两个真实案例,分析其根源及解决方法,希望能帮你在项目中少走弯路。
坑一:Arrays.asList() 与老版本 MyBatis 的反射陷阱
场景回放
某次上线后,测试反馈报了一个奇怪的异常:
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error evaluating expression 'userCode.size() > 0'...
这个异常看起来像是某个 size() 方法调用失败了,但代码里逻辑非常直观:
List<String> userCodes = Arrays.asList("aaa", "bbb", "ccc");
orderService.fetchOrderByUserCodes(userCodes);
SQL Mapper 里也只是简单判断:当 userCode 有值时才拼 IN 条件。
看上去没啥问题,但在老版 MyBatis 下,这段代码却偶现抛了反射访问权限错误。
根本问题分析
Arrays.asList()返回的并不是常见的java.util.ArrayList
它内部通过静态内部类Arrays$ArrayList实现List接口,但这个类是私有的。MyBatis 使用的是 OGNL 表达式引擎来判断
<if test="">的条件
OGNL 在解析表达式时,会通过反射调用size()方法。OGNL 的反射逻辑对私有类访问有特殊处理
因为这个类是私有的,OGNL 默认会调用setAccessible(true)来突破访问限制。老版 MyBatis 在设置
accessible时没有做好线程同步
多线程场景下,一个线程把accessible 改成true,另一个线程可能提前改回了false,就会导致反射调用失败。这种情况非常偶发。
总结下来,就是:
反射访问私有类的方法 + 并发环境 + 老版本 MyBatis 没有同步处理,才导致这个看似简单的
.size()调用失败。
解决方案
把
Arrays.asList()包装成真实的ArrayListList<String> userCodes = new ArrayList<>(Arrays.asList("aaa", "bbb", "ccc"));这是最直接的修复方式。
避免在 XML 表达式中调用方法,换成属性判断
<if test="userCode != null and userCode.length > 0">这样不需要触发反射方法调用。
升级 MyBatis 到现代版本
MyBatis 在 3.3.x 之后修复了这个并发访问的问题,因此升级是更根本的解决方案。
坑二:0 被 OGNL 当成空字符串导致 SQL 过滤条件消失
问题出现
某查询要求筛选状态为 0 的订单。代码和 Mapper 写得很正常:
return orderMapper.queryOrderByStatus(0);
对应 XML 条件是:
<if test="status != null and status != ''">
AND status = #{status}
</if>
本地测试没问题,但测试环境返回了所有订单(等同于没写 WHERE 条件)!
追查过程
先检查参数确实传成了 0,但拼出来的 SQL 却变成了:
SELECT * FROM t_order WHERE 1=1
也就是说 <if> 条件被判成了 false,SQL 条件被完全丢弃。
源头就是 OGNL 的比较规则
OGNL 在评估表达式 status != '' 时,会自动进行类型转换:
status是数值 0''是空字符串
它会先把两边都转成数值:0 <===> 0.0,结果就是 0 == '' 返回 true,所以 status != '' 判定为 false。
正确写法建议
因为数值类型根本不应该跟空字符串比较,应该这样写:
<if test="status != null">
AND status = #{status}
</if>
如果因为历史原因需要兼顾数值和字符串情况,可以显式写成:
<if test="status != null and (status != '' or status == 0)">
AND status = #{status}
</if>
核心结论:少犯这些表达式陷阱
1. 避免对基础类型使用空字符串判断
像 status != '' 这样的写法在 MyBatis/OGNL 下可能被错误解释,特别是数值类型。
2. 对集合类判断长度时避免反射方法
调用 size() 等方法可能触发反射访问,在某些版本里会因为安全 / 并发问题导致失败。
实用建议总结
这些坑都不是错代码本身,而是边缘情况下的表达式解析逻辑导致的问题。理解这些细节,可以让 MyBatis 的使用更稳健,避免在高并发或者边界条件下被“无声失败”消磨掉宝贵的排查时间。