Mybatis源码解析-BoundSql
mybatis 作为持久层,其操作数据库离不开 sql 语句。而 BoundSql 则是其保存 Sql 语句的对象
前提
针对 mybatis 的配置文件的节点解析,比如
where
/if
/trim
的节点解析可见文章Spring mybatis 源码篇章 -NodeHandler 实现类具体解析保存 Dynamic sql 节点信息针对 mybatis 配置文件的解析帮助类 SqlSource[一般为 DynamicSqlSource] 的使用可见文章Spring mybatis 源码篇章 -XMLLanguageDriver 解析 sql 包装为 SqlSource
对 BoundSql 对象的调用获取可见文章Mybatis 源码分析 -BaseExecutor
本文将在上述的知识前提下展开对 Sql 语句的解析
BoundSql 的引用
主要是通过MappedStatement#getBoundSql()
方法调用获取的。我们可以简单跟踪下其中的源码,如下
public BoundSql getBoundSql(Object parameterObject) {
// 通过 SqlSource 获取 BoundSql 对象
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
// 校验当前的 sql 语句有无绑定 parameterMapping 属性
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
<span class="hljs-comment">// check for nested result maps in parameter mappings (issue #30)</span>
<span class="hljs-keyword">for</span> (ParameterMapping pm : boundSql.getParameterMappings()) {
<span class="hljs-type">String</span> <span class="hljs-variable">rmId</span> <span class="hljs-operator">=</span> pm.getResultMapId();
<span class="hljs-keyword">if</span> (rmId != <span class="hljs-literal">null</span>) {
<span class="hljs-type">ResultMap</span> <span class="hljs-variable">rm</span> <span class="hljs-operator">=</span> configuration.getResultMap(rmId);
<span class="hljs-keyword">if</span> (rm != <span class="hljs-literal">null</span>) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
<span class="hljs-keyword">return</span> boundSql;
}
RawSqlSource- 常用的 mybatis 解析 sql 帮助类
我们观察下其 getBoundSql() 方法,源码如下
public BoundSql getBoundSql(Object parameterObject) {
// 此处的 sqlSource 为 RawSqlSource 的内部属性
return sqlSource.getBoundSql(parameterObject);
}
我们看下 sqlSource 是如何生成的,由此观察其构造函数
public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
// 通过 SqlSourceBuilder 来创建 sqlSource
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
Class<?> clazz = parameterType == null ? Object.class : parameterType;
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
}
#{}
的使用这里稍微提下,一般的写法都为{name,jdbcType=String,mode=out,javaType=java.lang.String...}
,其中 jdbcType 也可以不指定,系统会自动识别。上述的代码其实主要就是针对#{}
字符内容的处理注意:
${}
这样的字符是通过 DynamicSqlSource 来完成解析的,具体的解析读者可自行分析
我们可以继续看下 SqlSourceBuilder 类是如何解析获取 sql 语句的
SqlSourceBuilder#parse()
直接上源码
public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
// 对 #{} 这样的字符串内容的解析处理类
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// 获取真实的可执行性的 sql 语句
String sql = parser.parse(originalSql);
// 包装成 StaticSqlSource 返回
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
简单的看下 ParameterMappingTokenHandler 是如何解析的,其是TokenHandler
接口的实现类,我们就关注实现方法 handleToken
@Override
public String handleToken(String content) {
// 此处的作用就是对 `#{}` 节点中的 key 值保存映射,比如 javaType/jdbcType/mode 等信息,限于篇幅过长,读者可自行分析
parameterMappings.add(buildParameterMapping(content));
// 将 `#{}` 替换为?,即一般包装成 `select * form test where name=? and age=?` 预表达式语句
return "?";
}
上述主要通过
ParameterMappingTokenHandler
类来完成对#{}
字符串的解析,其中的映射信息则保存至 BoundSql 的 parameterMappings 属性中
总结
BoundSql 语句的解析主要是通过对
#{}
字符的解析,将其替换成?。最后均包装成预表达式供PrepareStatement
调用执行
#{}
中的 key 属性以及相应的参数映射,比如 javaType、jdbcType 等信息均保存至 BoundSql 的 parameterMappings 属性中供最后的预表达式对象PrepareStatement
赋值使用