MyBatis `` 标签:别再掉进 List 非空判断的坑里!
引言:线上 Bug 的血泪教训
话说 2026 年初,我接到一个紧急告警:线上订单服务突然出现大面积查询超时。经过一番排查,罪魁祸首竟然是一个看似人畜无害的 MyBatis XML 配置文件。
年轻的王二,为了赶进度,直接从 Stack Overflow 上抄了一段 <if test="list != null"> 的代码,用于判断订单 ID 列表是否为空。这段代码在本地测试环境跑得飞起,王二也就没多想,直接提交了。结果上线后,当传入一个空的订单 ID 列表时,SQL 并没有像预期的那样跳过 WHERE id IN (...) 这个条件,而是仍然执行了,导致数据库压力剧增,最终引发了雪崩。
更让人头疼的是,监控日志里根本看不出任何异常,因为 SQL 语句本身是合法的。这就像一个隐藏的定时炸弹,直到爆炸的那一刻,你才意识到问题的严重性。这种 Bug 的排查难度,啧啧,谁经历过谁知道。
深层原因分析:OGNL 的“障眼法”
为什么 list != null 在 MyBatis 中会失效?这得从 MyBatis 使用的 OGNL 表达式说起。 OGNL 表达式在判断 list != null 时,并非总是像 Java 代码那样直接判断 List 对象是否为 null。
在 MyBatis 的上下文中,OGNL 表达式的求值会受到很多因素的影响,比如:
- 参数传递:即使 List 本身是空的,但如果 MyBatis 上下文中还传递了其他参数,OGNL 可能会错误地认为整个上下文“不为空”,从而导致
list != null返回true。 - 类型转换:MyBatis 会进行一些类型转换,这可能会导致一些意想不到的结果。例如,如果 List 的类型不匹配,MyBatis 可能会尝试进行类型转换,这可能会导致
list != null的结果不符合预期。
说白了,MyBatis 里的 null 和 Java 里的 null,有时候不是一回事。这就像魔术师的障眼法,让你觉得你看到的就是真相,但实际上你已经被耍了。
“最佳实践”辨析:没有万能公式
别指望我给你一个“万能公式”,告诉你 List 非空判断应该怎么写。软件工程这行,哪有什么“银弹”?一切都要具体问题具体分析。
-
场景一:允许 List 为 null,也允许执行后续逻辑
这种情况下,简单的
list != null可能就足够了。例如,你的 SQL 只是根据 List 的值来添加一些额外的过滤条件,即使 List 为 null,也允许执行一个不带任何条件的查询。xml <select id="selectOrders" resultType="Order"> SELECT * FROM orders <where> <if test="list != null"> AND order_id IN <foreach item="item" collection="list" open="(" separator="," close=")"> #{item} </foreach> </if> </where> </select> -
场景二:List 必须非空才能执行特定 SQL 片段
这种情况下,
list != null and !list.isEmpty()才是更严谨的选择。isEmpty()方法相对于size() > 0在语义上更清晰地表达了“空”的概念,也更符合代码的可读性。xml <select id="selectOrders" resultType="Order"> SELECT * FROM orders <where> <if test="list != null and !list.isEmpty()"> AND order_id IN <foreach item="item" collection="list" open="(" separator="," close=")"> #{item} </foreach> </if> </where> </select> -
场景三:借助第三方库
可以使用 Apache Commons Collections 提供的
CollectionUtils.isNotEmpty(list)方法。优点是代码更简洁,缺点是引入了额外的依赖。 是否值得引入,取决于你的项目情况。xml <select id="selectOrders" resultType="Order"> SELECT * FROM orders <where> <if test="@org.apache.commons.collections4.CollectionUtils@isNotEmpty(list)"> AND order_id IN <foreach item="item" collection="list" open="(" separator="," close=")"> #{item} </foreach> </if> </where> </select>注意: 使用第三方库需要在 MyBatis 的配置文件中进行相应的配置,具体可以参考 MyBatis 的官方文档。
| 方法 | 优点 | 缺点 | 适用场景 | 潜在风险 |
|---|---|---|---|---|
list != null |
简单 | 可能存在 OGNL 表达式陷阱 | 允许 List 为 null,也允许执行后续逻辑 | 当 List 为空时,可能导致 SQL 语句执行不符合预期 |
list != null and !list.isEmpty() |
严谨,语义清晰 | 略显冗长 | List 必须非空才能执行特定 SQL 片段 | 无 |
CollectionUtils.isNotEmpty(list) |
代码简洁 | 引入额外依赖 | 代码简洁性优先,且项目已经引入了 Apache Commons Collections | 需要确保项目已经引入了 Apache Commons Collections,否则会报错 |
高级技巧:自定义 OGNL 方法
如果你对 MyBatis 的扩展性有更高的要求,可以考虑自定义 OGNL 方法。例如,可以自定义一个名为 isNotEmptyList 的方法,专门用于判断 List 是否非空。
-
编写自定义 OGNL 方法:
java public class OgnlUtils { public static boolean isNotEmptyList(List<?> list) { return list != null && !list.isEmpty(); } } -
配置 MyBatis 使用自定义方法:
在 MyBatis 的配置文件中,添加如下配置:
xml <configuration> <typeAliases> <typeAlias alias="OgnlUtils" type="com.example.OgnlUtils"/> </typeAliases> </configuration> -
在 XML 配置文件中使用自定义方法:
xml <select id="selectOrders" resultType="Order"> SELECT * FROM orders <where> <if test="@OgnlUtils@isNotEmptyList(list)"> AND order_id IN <foreach item="item" collection="list" open="(" separator="," close=")"> #{item} </foreach> </if> </where> </select>
这种方法的优点是可以将 List 非空判断的逻辑封装在一个地方,方便维护和复用。缺点是增加了代码的复杂度,需要对 MyBatis 的扩展机制有一定的了解。
代码审查 Checklist:防患于未然
为了避免再次发生类似王二的悲剧,我总结了一份代码审查 Checklist,希望能帮助大家在提交代码之前自查 MyBatis XML 配置文件中的 List 非空判断是否存在潜在问题:
- 是否考虑了 List 为 null 的情况?
- 是否使用了正确的 OGNL 表达式?
- 是否与业务逻辑一致?
- 是否经过了充分的测试?
- 是否考虑了 MyBatis 的类型转换可能带来的影响?
总结:魔鬼在细节中
List 非空判断看似简单,实则暗藏玄机。别轻信“万能公式”,多思考,多测试,才能避免掉进坑里。记住,魔鬼 往往藏在细节之中。
小心驶得万年船,希望这篇文章能帮助你写出更健壮的 MyBatis XML 配置文件,远离线上 Bug 的困扰!