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返回。DynamicSqlSource与StaticSqlSource的主要区别是:StaticSqlSource中记录了SQL语句中可能含有“?”占位符,但是可以直接提交给数据库执行;DynamicSqlSource中封装的SQL语句还需要进行一系列的解析,才会最终形成数据库可执行的SQL语句。DynamicSqlSource与RawSqlSource的区别在介绍RawSqlSource时会详细说明。
1.1. DynamicSqlSource
最先要介绍的是实践中最常用的SqlSource实现类——DynamicSqlSource,DynamicSqlSource中使用rootSqlNode字段(SqlNode类)记录了待解析的SqlNode树的根节点。DynamicSqlSource与MappedStatement以及SqlNode之间的关系如下图所示:

具体图示如下所示:

1.1.1. 参数
private final Configuration configuration;
private final SqlNode rootSqlNode;
1.1.2. 构造方法
DynamicSqlSource构造方法只是初始化了configuration和rootSqlNode的值。
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
RawSqlSource是SqlSource的另一个实现,其逻辑与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);
}
通过本节的分析,无论是StaticSqlSource、DynamicSqlSource还是RawSqlSource,最终都会统一生成BoundSql对象,其封装了完整的SQL语句(可能包含“?”占位符)、参数映射关系(parameterMappings集合)、以及用户传入的参数(additionalParameters集合)。另外,DynamicSqlSource负责处理动态SQL语句,RawSqlSource负责处理静态SQL语句。除此之外,两者解析SQL语句的时机是不一样的,前者的解析时机是在实际执行SQL语句的之前,而后者则是在Mybatis初始化是完成SQL语句的解析(原因见:XMLScript.parseScriptNode())。
2. SqlNode
SqlNode中使用了组合模式,故而Mybatis会将动态SQL节点解析成对应的SqlNode实现,并形成了一个树状结构。
本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!