当前位置: 首页 > news >正文

解决 MyBatis 中空字符串与数字比较引发的条件判断错误

问题复现

假设你在 MyBatis 的 XML 配置中使用了如下代码:

<if test="isCollect != null"><choose><when test="isCollect == 1">AND exists(select 1 from file_table imgfile2 where task.IMAGE_SEQ=imgfile2.IMAGE_SEQ and imgfile2.DOWNLOAD_STATUS = 1)</when><when test="isCollect == 0">AND not exists(select 1 from file_table imgfile2 where task.IMAGE_SEQ=imgfile2.IMAGE_SEQ and imgfile2.DOWNLOAD_STATUS = 1)</when></choose>
</if>

在这段代码中,通过 <choose> 标签对 isCollect 的值进行判断。如果 isCollect 的值为 1,则执行一个 exists 查询;如果 isCollect 的值为 0,则执行一个 not exists 查询。然而,实际运行时,当 isCollect空字符串 ("") 时,代码却会意外地执行到 test="isCollect == 0" 这一条件。
具体分析后,得出如果传入的 isCollect 是空字符串 "",由于 OGNL 类型转换的原因,空字符串会被转换为 0,导致条件判断意外地返回 true,从而执行 SQL 分支。本文将详细解析这个问题的根本原因,并提供有效的解决方案。

1. MyBatis 条件判断的执行流程

在 MyBatis 中,<if test="..."> 标签的条件表达式通过 OGNL(Object-Graph Navigation Language)引擎来解析。OGNL 可以动态地访问对象的属性,并执行表达式。MyBatis 将 test 属性中的表达式传递给 OGNL 引擎进行解析和计算,判断条件是否成立。

关键流程:
  • test 表达式解析test="isCollect == 0" 中的 isCollect == 0 会被 OGNL 解析并执行。
  • OGNL 类型转换:OGNL 在比较值时,会根据目标类型自动进行类型转换。例如,空字符串 "" 会被转换为 0(数字),导致条件 test="isCollect == 0" 被错误地评估为 true

2. 源码分析

要理解为什么空字符串 "" 被错误地转换为 0,我们需要查看 MyBatis 3.5.10 中的关键源码,特别是 OGNL 引擎如何处理这种类型转换。

a. IfSqlNode

IfSqlNode 负责解析 <if test="..."> 标签中的条件表达式。它会将 test 中的表达式交给 OGNL 引擎进行解析,然后根据条件结果决定是否生成 SQL 片段。

  • 源码路径org.apache.ibatis.scripting.xmltags.IfSqlNode
  • 主要方法apply(DynamicContext context)
# org.apache.ibatis.scripting.xmltags.IfSqlNode#apply@Overridepublic boolean apply(DynamicContext context) {if (evaluator.evaluateBoolean(test, context.getBindings())) {// 如果为true,追加SQL片段contents.apply(context);return true;}return false;}
# org.apache.ibatis.scripting.xmltags.OgnlCache#getValuepublic static Object getValue(String expression, Object root) {try {Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);// 获取OGNL表达式的值return Ognl.getValue(parseExpression(expression), context, root);} catch (OgnlException e) {throw new BuilderException("Error evaluating expression '" + expression + "'. Cause: " + e, e);}}private static Object parseExpression(String expression) throws OgnlException {Object node = expressionCache.get(expression);if (node == null) {node = Ognl.parseExpression(expression);expressionCache.put(expression, node);}return node;}

在这个方法中,Ognl.getValue 会评估 test 条件的结果。如果 test 返回 true,那么相应的 SQL 片段就会被拼接到最终的查询中。
在这里插入图片描述
在这里插入图片描述

b. OGNL 类型转换:OgnlRuntime.convertValue

OGNL 在执行表达式时会对输入的值进行类型转换,尤其是在数字与字符串比较时。如果传入的是一个空字符串,OGNL 会将其隐式地转换为数字 0,导致条件判断被误判为 true

  • 源码路径ognl.OgnlRuntime
  • 关键方法convertValue(Object value, Class targetType)
public static Object convertValue(Object value, Class targetType) {if (targetType == int.class || targetType == Integer.class) {if (value instanceof String) {return Integer.valueOf((String) value);  // 将空字符串转换为 0}}return value;
}

在这段代码中,如果 value 是一个字符串,OGNL 会尝试将其转换为 Integer。空字符串会被转换为 0,导致条件 isCollect == 0 被误评估为 true

3. 解决方案

为了避免空字符串被错误地转换为 0,在 test 条件中显式检查 isCollect 是否为 null 或空字符串。

a. 显式检查 null 和空字符串

通过添加显式的判断条件,可以确保 isCollect 既不为 null 也不为空字符串,从而避免 test="isCollect == 0" 误判。改写后的 SQL 语句如下:

<if test="isCollect != null and isCollect != ''"><choose><when test="isCollect == 1">AND exists(select 1 from file_table imgfile2 where task.IMAGE_SEQ=imgfile2.IMAGE_SEQ and imgfile2.DOWNLOAD_STATUS = 1)</when><when test="isCollect == 0">AND not exists(select 1 from file_table imgfile2 where task.IMAGE_SEQ=imgfile2.IMAGE_SEQ and imgfile2.DOWNLOAD_STATUS = 1)</when></choose>
</if>

通过这种方式,我们可以确保只有在 isCollect 不为 null 且不为空字符串时,才会进行 test="isCollect == 0" 判断,避免空字符串误判为 0

4. 总结

  • OGNL 表达式:在 MyBatis 中,test="isCollect == 0" 会使用 OGNL 解析,空字符串会被隐式转换为 0,导致条件判断错误。
  • 类型转换:OGNL 会自动将空字符串 "" 转换为数字 0,从而导致 test="isCollect == 0" 被误判为 true
  • 解决方案:通过显式检查 isCollect != null && isCollect != '' 来避免空字符串被误判为 0

参考资料

  • MyBatis 官方文档:MyBatis
http://www.lryc.cn/news/501919.html

相关文章:

  • python 词向量的代码解读 self.word_embeds = nn.Embedding(vocab_size, embedding_dim) 解释下
  • 记一次:使用C#创建一个串口工具
  • Android Studio新版本的一个资源id无法找到的bug解决
  • Datawhale AI冬令营(第一期)--零基础定制你的专属大模型
  • LLMs之APE:基于Claude的Prompt Improver的简介、使用方法、案例应用之详细攻略
  • 【Unity人形布娃娃插件】Ragdoll Animator
  • 跨团队协作中目标一致性至关重要
  • Excel的文件导入遇到大文件时
  • 使用字典进行动态编程
  • 机器学习02-发展历史补充
  • 全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之计数器与累加器(一)
  • Android的SurfaceView和TextureView介绍
  • Scala的集合
  • 1. Flink自定义Source
  • 关于LinuxWindows双系统在八月更新后出现的问题
  • VMware:如何在CentOS7上开启22端口
  • ubuntu远程桌面开启opengl渲染权限
  • 从小学题到技术选型哲学:以智能客服系统为例,解读相关AI技术栈20241211
  • 【C语言练习(5)—回文数判断】
  • 【Rust 学习笔记】Rust 基础数据类型介绍——数组、向量和切片
  • 2024年特别报告,「十大生活方式」研究数据报告
  • R中单细胞RNA-seq分析教程 (5)
  • openpnp - Too many misdetects - retry and verify fiducial/nozzle tip detection
  • 不与最大数相同的数字之和
  • CSS学习记录11
  • D95【python 接口自动化学习】- pytest进阶之fixture用法
  • Abaqus断层扫描三维重建插件CT2Model 3D V1.1版本更新
  • 隐式对象和泛型
  • CSS的颜色表示方式
  • 单链表常见面试题 —— LeetCode