【线上踩坑分享】MyBatis if标签的坑,你踩过吗?

问题现象

我们知道MyBatis通过OGNL表达式的方式来进行动态SQL的拼接,比如像下图所示:if标签代表判断逻辑,但你是否了解,过果type类型为数值时,那么对于type != ''部分判断会出现一些问题,比如,当type为0时,type != ''结果为false,而当type为非0数值时type != ''结果为true

xml 复制代码
<if test="type != null and type != ''"> 
</if>

问题分析

此类问题只能通过翻阅源码了解原因。

首先找到MyBatis中对于if标签的解析部分,如下图所示:

java 复制代码
public class IfSqlNode implements SqlNode {
  private final ExpressionEvaluator evaluator;
  private final String test;
  private final SqlNode contents;

  public IfSqlNode(SqlNode contents, String test) {
    this.test = test;
    this.contents = contents;
    this.evaluator = new ExpressionEvaluator();
  }

  @Override
  public boolean apply(DynamicContext context) {
    if (evaluator.evaluateBoolean(test, context.getBindings())) {
      contents.apply(context);
      return true;
    }
    return false;
  }

}

判断逻辑在evaluateBoolean方法中,其中参数expression就是"type != null and type != ''"

java 复制代码
public boolean evaluateBoolean(String expression, Object parameterObject) {
  Object value = OgnlCache.getValue(expression, parameterObject);
  if (value instanceof Boolean) {
    return (Boolean) value;
  }
  if (value instanceof Number) {
    return new BigDecimal(String.valueOf(value)).compareTo(BigDecimal.ZERO) != 0;
  }
  return value != null;
}

继续查看OgnlCache.getValue(expression, parameterObject)方法

java 复制代码
public static Object getValue(String expression, Object root) {
  try {
    Map context = Ognl.createDefaultContext(root, MEMBER_ACCESS, CLASS_RESOLVER, null);
    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(parseExpression(expression), context, root)方法,先看parseExpression(expression)方法,这个方法实际上就是解析了"type != null and type != ''"这段内容,然后返回给Ognl.getValue方法来处理。

经过上面的源码分析后,可以判断if中的内容主要是通过Ognl表达式来决定,所以我们就来验证Ognl表达式对于"type != null and type != ''"这个内容的判断。

OGNL表达式测试

java 复制代码
public class Main {
    public static void main(String[] args) throws OgnlException {
        System.out.println(Ognl.parseExpression("0 != null and 0 != ''"));
    }
}

可以看到"0 != null and 0 != ''"先是被解析为了(0 != null) && (0 != "")这样一段内容,接着我们放入Ognl.getValue方法中再来看看结果。

java 复制代码
public class Main {
    public static void main(String[] args) throws OgnlException {
        Object value = Ognl.getValue(Ognl.parseExpression("0 != null and 0 != ''"), null);
        System.out.println(value);
    }
}

结果为false

我们可以继续再对其他类型进行测试。

java 复制代码
public class Main {
    public static void main(String[] args) throws OgnlException {
        Object value1 = Ognl.getValue(Ognl.parseExpression("1 != null and 1 != ''"), null);
        Object value2 = Ognl.getValue(Ognl.parseExpression("2 != null and 2 != ''"), null);
        Object value3 = Ognl.getValue(Ognl.parseExpression("-1 != null and -1 != ''"), null);
        System.out.println(value1);
        System.out.println(value2);
        System.out.println(value3);
    }
}

结果都为true,也就是说应该只有为0时结果才为false

接下来,我们将表达式部分拆开分别进行实验。

先来测试"type != null"

java 复制代码
public class Main {
    public static void main(String[] args) throws OgnlException {
        Object value1 = Ognl.getValue(Ognl.parseExpression("1 != null"), null);
        Object value2 = Ognl.getValue(Ognl.parseExpression("2 != null"), null);
        Object value3 = Ognl.getValue(Ognl.parseExpression("-1 != null"), null);
        Object value4 = Ognl.getValue(Ognl.parseExpression("0 != null"), null);
        System.out.println(value1);
        System.out.println(value2);
        System.out.println(value3);
        System.out.println(value4);
    }
}

再来测试"type != ''"

java 复制代码
public class Main {
    public static void main(String[] args) throws OgnlException {
        Object value1 = Ognl.getValue(Ognl.parseExpression("1 != ''"), null);
        Object value2 = Ognl.getValue(Ognl.parseExpression("2 != ''"), null);
        Object value3 = Ognl.getValue(Ognl.parseExpression("-1 != ''"), null);
        Object value4 = Ognl.getValue(Ognl.parseExpression("0 != ''"), null);
        System.out.println(value1);
        System.out.println(value2);
        System.out.println(value3);
        System.out.println(value4);
    }
}

测试后表明,错误的结果应该就是由"type != ''"判断造成的。

查询Ognl官方文档验证

文档中已经说明,当if中的对象为Number类型时,将使用双精度浮点类型与0比较,如果等于0则视为false,否则视为true

解决方式

通过前面的测试验证和官方文档说明可以看出,主要问题就是Number类型的判断,我们只需要改写成如下这样就可以了。

xml 复制代码
<if test="type != null"> 
</if>

不过,如果type为数值类型,本就不应该在让它去和空字符串进行比较,这在Java语言中编译就无法通过。

相关推荐
长栎18 分钟前
写 for 循环写了十年,你却从没用过迭代器模式最狠的那一面
后端
LiaCode22 分钟前
Redis 在生产项目的使用
前端·后端
用户5598224812226 分钟前
Docker Compose Down 导致容器数据误删——ext4 日志恢复全记录
后端
LiaCode27 分钟前
一天学完 redis 的爽翻版核心知识总结
前端·后端
大刚测试开发实战28 分钟前
如何内网穿透访问本地私有化部署的TestHub
前端·后端·github
xiaodaoluanzha1 小时前
迄今為止,最簡單的編程語言 Nolang
前端·后端
Csvn1 小时前
Docker 容器管理入门 — 从镜像到容器编排
后端
用户762352425911 小时前
ShardingJDBC
后端
行者全栈架构师1 小时前
IDEA 中 Maven 项目的 15 个红色报错快速解决方法
java·后端
令人头秃的代码0_01 小时前
mac(m5)平台编译openjdk
java