当前位置:首页 > 技术 > 正文内容

MyBatis if 标签的坑,居然被我踩到了。。。

fengm2个月前 (12-09)技术180

事件的原因是这样的,需求是按条件查数据然后给前端展示就行了,写的时候想着挺简单的,不就是使用 MyBatis 动态 SQL 去查询数据吗?

现实还是很残酷的,等我写完上完 UAT 后,前端同学说根据state查的数据与理想的数据不一致,这个state当时设计时只有两个值:01

/**
 * 数据状态
 */
@Range(min = 0, max = 1, message = "状态只能为0(未处理),1(已处理)")
private Integer state;

理想情况下通过前端传递过来的值,然后进行sql查询就可以了:

<if test="req.state != null and req.state != ''">
            AND md.state = #{req.state}
</if>

上面的sql首先判断state不为空且不为空字符串时,然后添加比较state字段。初步看下来if判断没什么问题,但是我传递进去的req.stateInteger型的,仔细查看req.state != null没毛病,然后发现req.state != ''使用Integer与空字符串做比较。

前端在查询的时如果没有传递req.statereq.state != null 这里不会满足,但是前端传递了一个0过来的时候req.state != ''居然返回的是false也就是说在MyBatis的if语法中0是等于空字符串的

{
	"state": 0
}

这样的比较没有报错,也是有点想不通了,没办法只能去看MyBatis源码找出这原因。

查看 MyBatis 源码

MyBatis 其他源码的查找过程就不详细说了,这里直接找到XMLScriptBuilder类,找到if语法的解析过程,然后一步步的探究0 == ''的原因。 XMLScriptBuilder会解析trimif等 MyBatis 支持的语法,它的解析原理是通过NodeHandler来分别解析不同的标签:

  private void initNodeHandlerMap() {
    nodeHandlerMap.put("trim", new TrimHandler());
    nodeHandlerMap.put("where", new WhereHandler());
    nodeHandlerMap.put("set", new SetHandler());
    nodeHandlerMap.put("foreach", new ForEachHandler());
    nodeHandlerMap.put("if", new IfHandler());
    nodeHandlerMap.put("choose", new ChooseHandler());
    nodeHandlerMap.put("when", new IfHandler());
    nodeHandlerMap.put("otherwise", new OtherwiseHandler());
    nodeHandlerMap.put("bind", new BindHandler());
  }

由于是不正解的语法是if标签,查看IfHandler就好了,其他现在略过就好。

 private class IfHandler implements NodeHandler {
    public IfHandler() {
      // Prevent Synthetic Access
    }

    @Override
    public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
      String test = nodeToHandle.getStringAttribute("test");
      IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      targetContents.add(ifSqlNode);
    }
  }

MyBatis会将if标签抽象成IfSqlNode

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;
  }

}

终于有一点眉头了, MyBatis 会将if标签的test属性使用ExpressionEvaluator测试一下是否为true或者为false

public class ExpressionEvaluator {

  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;
  }

  public Iterable<?> evaluateIterable(String expression, Object parameterObject) {
    Object value = OgnlCache.getValue(expression, parameterObject);
    if (value == null) {
      throw new BuilderException("The expression '" + expression + "' evaluated to a null value.");
    }
    if (value instanceof Iterable) {
      return (Iterable<?>) value;
    }
    if (value.getClass().isArray()) {
        // the array may be primitive, so Arrays.asList() may throw
        // a ClassCastException (issue 209).  Do the work manually
        // Curse primitives! :) (JGB)
        int size = Array.getLength(value);
        List<Object> answer = new ArrayList<Object>();
        for (int i = 0; i < size; i++) {
            Object o = Array.get(value, i);
            answer.add(o);
        }
        return answer;
    }
    if (value instanceof Map) {
      return ((Map) value).entrySet();
    }
    throw new BuilderException("Error evaluating expression '" + expression + "'.  Return value (" + value + ") was not iterable.");
  }

}

最后得到结论:Mybatis 使用的 Ognl表达式 来获取 test 属性的值

最终论证

已经知道 MyBatis 内部是使用的 Ognl表达式 ,是不是 Ognl表达式 的引起的呢? 实践一下就知道了,先引入依赖:

<!-- https://mvnrepository.com/artifact/ognl/ognl -->
<dependency>
    <groupId>ognl</groupId>
    <artifactId>ognl</artifactId>
    <version>2.7.3</version>
</dependency>

写程序测试:

    public static void main(String[] args) {

        Map<String, Object> objectMap = new HashMap<>();
        objectMap.put("state", 0);
        Object value = OgnlCache.getValue("state == ''", objectMap);
        System.out.println(value);
    }

上面程序输出的真的是true。。。

总结

真是脑袋抽筋啊,Integer还判断是否为空字符串。。。

记录此坑,希望对大家有所帮助。

推荐

欢迎关注公众号:架构文摘,获得独家整理120G的免费学习资源助力你的架构师学习之路!

公众号后台回复arch028获取资料:

扫描二维码至手机访问

扫描二维码推送至手机访问。

版权声明:本文由风芒博客新闻信息分享笔录网站发布,如需转载请注明出处。

转载请注明出处:http://fengm.top/715.html

分享给朋友:

相关文章

网站死链查询检测方法和怎么剔除死链

网站死链查询检测方法和怎么剔除死链

  网站死链的影响是非常大的,一个网站过多的死链不仅会造成蜘蛛道路不通,还会影响网站形象、整体收录和排名,所以整个网站的死链是必须要杜绝的。 下面分享网站死链查询、死链删除提交及避免方法。 1...

几大排序算法的理解和代码实现(超级详细的过程)

几大排序算法的理解和代码实现(超级详细的过程)

几种常见的排序(比较) 冒泡排序 这里就按照下面的两步模拟冒泡排序: 后面的类似,就不展示。 Code: #include <iostream> using namesp...

seo必备白帽常识

seo必备白帽常识

  1、网站更新有规律,每天更新或有规律几天一更新 2、内容评论中出现最好出现关键词 3、内容靠前部和最后部分出现关键词 4、按照web2.0标准建站,最好通过W3C验证 5、找内容相关页...

干货 百度快照长期不更新原因分析

干货 百度快照长期不更新原因分析

  就网站活动而言。几乎每天都更新网站内容。百度喜欢快速更新的网站,喜欢有新内容;这不是导致百度不更新快照的原因。对百度内相同 IP下其他域名的搜录情况进行分析。基础很好,没有任何站点。然后,同服...

分享:

支付宝

微信