【线上踩坑分享】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语言中编译就无法通过。

相关推荐
IT_陈寒7 分钟前
JavaScript性能飞跃:5个V8引擎优化技巧让你的代码提速300%
前端·人工智能·后端
骑士雄师18 分钟前
Java 泛型中级面试题及答案
java·开发语言·面试
Victor35620 分钟前
Redis(61)Redis的连接数上限是多少?
后端
Victor35624 分钟前
Redis(60) Redis的复制延迟如何优化?
后端
.格子衫.6 小时前
Spring Boot 原理篇
java·spring boot·后端
多云几多6 小时前
Yudao单体项目 springboot Admin安全验证开启
java·spring boot·spring·springbootadmin
Jabes.yang8 小时前
Java求职面试实战:从Spring Boot到微服务架构的技术探讨
java·数据库·spring boot·微服务·面试·消息队列·互联网大厂
聪明的笨猪猪9 小时前
Java Redis “高可用 — 主从复制”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
兮动人9 小时前
Spring Bean耗时分析工具
java·后端·spring·bean耗时分析工具
小陈爱coding9 小时前
SaaS多租户数据隔离实战:MyBatis拦截器实现行级安全方案
安全·云原生·mybatis·多租户