SqlNode&SqlSource

根据Mybatis初始化过程可知,映射配置文件中定义的SQL节点会被解析成MapperStatement对象,其中的SQL语句会被解析成SqlSource对象,SQL语句中定义的动态SQL节点、文本节点等,则有SqlNode接口的相应实现表示,示例如下所示:

 <select id="selectByAuthorById" resultMap="authorResultMap">
    select * from tb_author
    <if test="list != null and list.size() != 0">
      where author_id in
      <foreach collection="list" item="item" index="index" open="(" separator="," close=")">
        #{item}
      </foreach>
    </if>

    <if test="list != null and list.size() != 0">
      where author_id = 1 
    </if>

  </select>

上述语句的SqlSource的结构为,如图所示:

1. SqlSource

SqlSource的实现图如下所示:

这里对SqlSource接口的各个实现做简单说明:DynamicSqlSource负责动态SQL语句,RawSqlSource负责处理静态SQL语句,两者最终都会将处理后的SQL语句封装成StaticSqlSource返回。DynamicSqlSourceStaticSqlSource的主要区别是:StaticSqlSource中记录了SQL语句中可能含有“?”占位符,但是可以直接提交给数据库执行;DynamicSqlSource中封装的SQL语句还需要进行一系列的解析,才会最终形成数据库可执行的SQL语句。DynamicSqlSourceRawSqlSource的区别在介绍RawSqlSource时会详细说明。

1.1. DynamicSqlSource

最先要介绍的是实践中最常用的SqlSource实现类——DynamicSqlSourceDynamicSqlSource中使用rootSqlNode字段(SqlNode类)记录了待解析的SqlNode树的根节点。DynamicSqlSourceMappedStatement以及SqlNode之间的关系如下图所示:

具体图示如下所示:

1.1.1. 参数

  private final Configuration configuration;

  private final SqlNode rootSqlNode;

1.1.2. 构造方法

DynamicSqlSource构造方法只是初始化了configurationrootSqlNode的值。

  public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
    this.configuration = configuration;
    this.rootSqlNode = rootSqlNode;
  }

1.1.3. getBoundSql()

DynamicSqlSource中只有一个getBoundSql()方法,这个方法在用户调用Mapper接口中的方法的时候才会被调用(即:MappedStatement.getBounSql()的时候才能被调用),因为它的作用就是将SQL语句中的“#{}”占位符替换成”?“占位符,创建BoundSql对象,并将DynamicContext.bindings中的参数信息复制到additionalParameters集合中保存,具体如下所示:

@Override
  public BoundSql getBoundSql(Object parameterObject) {
    //创建DynamicContext对象,parameterObject使用传入的实参
    DynamicContext context = new DynamicContext(configuration, parameterObject);
    //通过调用rootSqlNode.apply()方法调用整个树形结构中全部的SqlNode.apply()方法,
    // 每个SqlNode的apply()方法都会将解析得到的SQL语句片段追加到context中,最终通过
    //context.getSql()得到完整的SQL语句
    rootSqlNode.apply(context);
    //创建SqlSourceBuilder,解析参数属性,并将SQL语句中的“#{}”占位符替换成"?"占位符
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass();
    SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings());
    //创建BoundSql对象,并将DynamicContext.bindings中的参数信息复制到additionalParameters集合中保存
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    context.getBindings().forEach(boundSql::setAdditionalParameter);
    return boundSql;
  }

DynamicSqlSource的具体实现就介绍到这里。

1.2. RawSqlSource

RawSqlSourceSqlSource的另一个实现,其逻辑与DynamicSqlSource类似,但是执行时机是不一样的,处理的SQL语句类型也是不一样的。前面介绍XMLScriptBuilder.parseDynamicTags()方法时提到过,如果节点只包含“#{}”占位符,而不包含动态SQL节点或未解析的“${}”占位符的话,这不是动态SQL语句,会创建相应的StaticTextSqlNode对象。在XMLScriptBuider.parseSctiptNode()方法中会判断整个SQL节点是否为动态的,如果不是动态的SQL节点,则创建相应的RawSqlSource对象。

RawSqlSource在构造方法中首先会调用getSql()方法,其中通过调用SqlNode.apply()方法完成SQL语句的拼装和初步处理,之后会使用SqlSourceBuilder完成占位符的替换(*也就是说如果是RawSqlSource类型的SQL节点,在这个时候就完成了从#{} 到?号的转变*)和ParameterMapping集合的创建,并返回StaticSqlSource对象。这两个过程的具体实现前面已经介绍过了,不在重复。

1.2.1. 参数

 private final SqlSource sqlSource;

1.2.2. 构造方法

 public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    //调用getSql()方法,完成SQL语句的拼装
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }

  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
    //通过SqlSourceBuilder完成占位符的拼装和初步解析  从#{}   --->?的转变
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>());
  }

重载的构造方法会通过SqlSourceBuilder完成占位符的拼装和初步解析 从“#{} ”到“?”的转变。

1.2.3. getSql()

getSql()会被构造方法调用,作用是完成SQL语句的拼装。

  private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    rootSqlNode.apply(context);
    return context.getSql();
  }

1.2.4. getBoundSql()

 @Override
  public BoundSql getBoundSql(Object parameterObject) {
    return sqlSource.getBoundSql(parameterObject);
  }

通过本节的分析,无论是StaticSqlSourceDynamicSqlSource还是RawSqlSource,最终都会统一生成BoundSql对象,其封装了完整的SQL语句(可能包含“”占位符)、参数映射关系(parameterMappings集合)、以及用户传入的参数(additionalParameters集合)。另外,DynamicSqlSource负责处理动态SQL语句,RawSqlSource负责处理静态SQL语句。除此之外,两者解析SQL语句的时机是不一样的,前者的解析时机是在实际执行SQL语句的之前,而后者则是在Mybatis初始化是完成SQL语句的解析(原因见:XMLScript.parseScriptNode())。

2. SqlNode

SqlNode中使用了组合模式,故而Mybatis会将动态SQL节点解析成对应的SqlNode实现,并形成了一个树状结构。



Mybatis源码分析   核心处理层      Mybatis源码分析

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!