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协议 。转载请注明出处!