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

问题复现

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

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)
java 复制代码
# org.apache.ibatis.scripting.xmltags.IfSqlNode#apply
  @Override
  public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
        // 如果为true,追加SQL片段
      contents.apply(context);
      return true;
    }
    return false;
  }
java 复制代码
# org.apache.ibatis.scripting.xmltags.OgnlCache#getValue

  public 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)
java 复制代码
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 语句如下:

xml 复制代码
<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

参考资料

相关推荐
ssxueyi38 分钟前
Java 常见Exception异常解决方法
java·开发语言·数据库·异常处理·exception
xzq_java41 分钟前
Javafx.麦当劳点餐系统(Java简洁版)
java·sql
silver68742 分钟前
jvm内存优化
java
lzz的编码时刻44 分钟前
观察者模式:事件处理机制与松耦合设计
java·开发语言·设计模式
yun_shui_1 小时前
【力扣-KMP】28.找出字符串第一个匹配项的下标
java·数据结构·算法·leetcode
lvyuanj1 小时前
IDEA skywalking 启动报错 ClassNotFoundException InstanceConstructorInterceptor
java·intellij-idea·skywalking
上海拔俗网络1 小时前
“AI全网络深度学习系统:开启智能时代的新篇章
java·团队开发
轩辕Ruins2 小时前
idea无法编译src/main/java下的xml或者properties文件
java·intellij-idea
Tech Synapse2 小时前
Java 动态设置 JVM 参数的方法
java·开发语言·jvm