38

不学无数——Mybatis解析判断表达式源码分析

 5 years ago
source link: https://studygolang.com/articles/15449?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Mybatis解析判断表达式源码分析

在我们开发过程中用 Mybatis 经常会用到下面的例子

Mapper如下

Map<String ,String > testArray(@Param("array") String [] array);

XMl中的sql如下

<select id="testArray" resultType="map">
    select * from t_ams_ac_pmt_dtl where  cpt_pro=#{cptProp}
    <if test="array!=null and array != '' ">
        and cpt_pro=#{cptProp}
    </if>
</select>

刚看上面的代码会觉得数组怎么能和空字符串进行一起比较呢,一开始会觉得这个代码运行起来绝对报错,但是写单元测试运行了一遍发现成功运行了。因此想是不是 Mybatis 在内部对数组类型的数据进行了封装。于是有了这一次的源码解析之旅。上网查了查发现  Mybatis 解析使用了  OGNL 。至于什么是  OGNL 摘抄了百度百科中的一段话

OGNL是Object-Graph Navigation Language的缩写,它是一种功能强大的表达式语言,通过它简单一致的表达式语法,可以存取对象的任意属性,调用对象的方法,遍历整个对象的结构图,实现字段类型转化等功能。它使用相同的表达式去存取对象的属性。这样可以更好的取得数据。

单元测试类如下

@Test
    public void testArray(){
        SqlSession sqlSession = sqlSessionFactory.openSession();
        TBapCheckPtsTranscdMapper mapper = sqlSession.getMapper(TBapCheckPtsTranscdMapper.class);
        String str= "1,2,3";
        String [] strings = str.split(",");
        mapper.testArray(strings);
    }

首先我们先来看一下 DynamicSqlSource 这个类,这个类中有个方法如下

@Override
  public BoundSql getBoundSql(Object parameterObject) {
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    rootSqlNode.apply(context);
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    for (Map.Entry<String, Object> entry : context.getBindings().entrySet()) {
      boundSql.setAdditionalParameter(entry.getKey(), entry.getValue());
    }
    return boundSql;
  }

其中

rootSqlNode.apply(context);

这段代码对SQL进行了动态的拼接,然后点进去看一下

@Override
  public boolean apply(DynamicContext context) {
    for (SqlNode sqlNode : contents) {
      sqlNode.apply(context);
    }
    return true;
  }

这里的SQL拼接运用了 组合模式 不同的  sqlNode 调用的方法不一样,但是最后的想要结果都是一样的:拼接SQL。例如我们第一次进  apply 这个方法中的时候他跳转到了

StaticTextSqlNode 这个类中调用了下面的方法

@Override
  public boolean apply(DynamicContext context) {
    context.appendSql(text);
    return true;
  }

直接将SQL拼接为

select * from t_ams_ac_pmt_dtl where  cpt_pro=#{cptProp}

然后我们第二次循环执行发现它跳转到了 IfSqlNode 这个类中,这是标签为  <if> 的判断类,

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

在解析语句中传了两个参数进去

evaluator.evaluateBoolean(test, context.getBindings())
  • test  :就是要解析的表达式,在此场景下就是  array!=null and array != ''
  • context.getBindings()  :获得的是一个Map,其中存储了参数  array  的所对应的值,如下所示

buEB3qu.jpg!web

image

然后接下来就到了 OGNL 解析表达式了,发现最后到了  ASTNotEq 这类中

protected Object getValueBody(OgnlContext context, Object source) throws OgnlException {
        Object v1 = this._children[0].getValue(context, source);
        Object v2 = this._children[1].getValue(context, source);
        return OgnlOps.equal(v1, v2) ? Boolean.FALSE : Boolean.TRUE;
    }

这里解析分为了两步进行解析,上面的表达式为 array!=null and array != '' 那么他会根据and 进行分组将其放入  Node 数组中。

  • Node[0]  :  array!=null
  • Node[1]  :  array != ''

然后这里面的两个参数 v1 和  v2 分别为左边和右边的参数,此时先解析  Node[0] 中的参数

  • v1  :就是参数  array  对应的数组的值
  • v2  :就是null

此时到这应该就知道为什么 String 数组为什么能和空字符串进行比较了,因为他将数组转化为了  Object 然后用自己写的  equal 方法进行比较。然后进去他写的  equal 方法中看了以后发现他对数组比较是特殊的。

  • 如果左边是数组右边是字符串:两个都转换为  Object  然后进行  v1.getClass()==v2.getClass()  判断
  • 如果左边是数组右边也是数组:先判断两个数组的长度是否相同,如果相同,那么循环遍历两个数组进行里面的值的比较

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK