在前一篇文章深入浅出Mybatis源码解析——映射文件加载流程中,最后说到了创建SqlSource和创建MappedStatement对象,由于篇幅原因最后只好终止了,所以便只好在写一篇文章来说说SqlSource这样的一个创建流程是怎样的,在本系列第一篇完成后,也曾问过一两个认识的读者,有说不太看得懂,可能本人写博客不够图文并茂,所以在下一篇博文中准备来整理一张Mybatis的一个完整的执行流程图,来带读者清楚的了解一下Mybatis的这样一个执行流程。
本篇文章即将开始,请系好安全带!
再正式开始之前,先来看下createSqlSource方法的代码具体实现,代码如下:
@Override
public SqlSource createSqlSource(Configuration configuration, XNode script, Class parameterType) {
// 初始化了动态SQL标签处理器
XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
// 解析动态SQL
return builder.parseScriptNode();
}
注意这个方法是在XMLLanguageDriver.java这个类中,既然我们看到了入口的地方,那就看看Mybatis的SqlSource是如创建的,上面的代码中首先构建了XMLScriptBuilder这个对象,我觉得有必要看看这个构造方法做了什么,因为我感觉这个构造函数不同凡响。废话不多说,直接上代码:
public XMLScriptBuilder(Configuration configuration, XNode context, Class parameterType) {
super(configuration);
this.context = context;
this.parameterType = parameterType;
// 初始化动态SQL中的节点处理器集合
initNodeHandlerMap();
}
private void initNodeHandlerMap() {
nodeHandlerMap.put("trim", new TrimHandler());
nodeHandlerMap.put("where", new WhereHandler());
nodeHandlerMap.put("set", new SetHandler());
nodeHandlerMap.put("foreach", new ForEachHandler());
nodeHandlerMap.put("if", new IfHandler());
nodeHandlerMap.put("choose", new ChooseHandler());
nodeHandlerMap.put("when", new IfHandler());
nodeHandlerMap.put("otherwise", new OtherwiseHandler());
nodeHandlerMap.put("bind", new BindHandler());
}
这就是不同凡响?可能有人是怀疑的,但是你看看initNodeHandlerMap方法中存了这么多对象,这些对象基本上都是mapper文件中的一些where、foreach、if等这些标签对象。因此我想着nodeHandlerMap肯定不是个普通的map集合。那我们来简单的看下nodeHandlerMap:
private final Map nodeHandlerMap = new HashMap<>();
private interface NodeHandler {
void handleNode(XNode nodeToHandle, List targetContents);
}
private class BindHandler implements NodeHandler {
public BindHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List targetContents) {
final String name = nodeToHandle.getStringAttribute("name");
final String expression = nodeToHandle.getStringAttribute("value");
final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
targetContents.add(node);
}
}
private class WhereHandler implements NodeHandler {
public WhereHandler() {
// Prevent Synthetic Access
}
@Override
public void handleNode(XNode nodeToHandle, List targetContents) {
// 混合SQL节点
MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
targetContents.add(where);
}
}
从上面的代码中可以看出,nodeHandlerMap的存储的是NodeHandler这么个内部接口对象,那么在initNodeHandlerMap方法中所存的对象肯定是去实现NodeHandler的。因此在这里列出了两个相关的实现类代码,对于WhereHandler实现的代码里的parseDynamicTags方法,接下来我们会进行说明,因为那些实现的对象中,大多数调用了这个方法。我们还是继续回头看下builder.parseScriptNode()。
builder.parseScriptNode()的调用还是在createSqlSource方法中,既然如此,那便看看这个parseScriptNode方法到底做了什么:
public SqlSource parseScriptNode() {
// 获取解析过的SQL信息(解析了动态SQL标签和${})
// 注意:此时SQL语句中还有#{}没有处理
MixedSqlNode rootSqlNode = parseDynamicTags(context);
SqlSource sqlSource = null;
// 如果包含${}和动态SQL语句,就是dynamic的
if (isDynamic) {
sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
} else {
// 否则是RawSqlSource的(带有#{}的SQL语句)
sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
}
return sqlSource;
}
方法很简单,就是首先通过parseDynamicTags方法来获得一个MixedSqlNode对象,接着开始判断这个SQL是否是动态的,判断的标准是如果包含${}和动态SQL语句,那么就是dynamic的SQL,否则就是RawSqlSource。可能有同学在想这个isDynamic是怎么来的,不要着急等下就会揭晓这个问题。
我们先来看下parseDynamicTags到底做了什么,然后分别看下DynamicSqlSource和RawSqlSource对象的构造方法。
protected MixedSqlNode parseDynamicTags(XNode node) {
List contents = new ArrayList<>();
//获取
上面的代码逻辑还是比较简单的,在获得那些标签的子元素后,然后对其进行遍历,在便利的时候来判断是文本节点还是元素节点,在这里我们可以看到isDynamic被置为true的操作。这里最后把所有获取出来的数据放到集合中,然后在把这个集合放到MixedSqlNode这个对象中,然后返回。
刚才说了要看看,DynamicSqlSource和RawSqlSource类,其实这两个类构造方法的代码很简单:
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) {
this.configuration = configuration;
this.rootSqlNode = rootSqlNode;
}
public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class parameterType) {
this(configuration, getSql(configuration, rootSqlNode), parameterType);
}
哎……这个RawSqlSource的构造函数好像不是很简单啊,里面调用了this,而且还调用了getSql方法,那就顺便看下:
public RawSqlSource(Configuration configuration, String sql, Class parameterType) {
// 解析SQL语句
SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
// 获取入参类型
Class clazz = parameterType == null ? Object.class : parameterType;
// 开始解析
sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap());
}
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
DynamicContext context = new DynamicContext(configuration, null);
rootSqlNode.apply(context);
return context.getSql();
}
貌似又发现了新大陆,在调this的方法里,首先创建了SqlSourceBuilder对象,然后获取parameterType,最后进行解析,这个解析我们等下再看,先看看getSql里做了什么,貌似并没做什么,就是先创建DynamicContext对象,最后通过动态上下文去获取SQL。
public SqlSource parse(String originalSql, Class parameterType, Map additionalParameters) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType,
additionalParameters);
// 创建分词解析器
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
// 解析#{}
String sql = parser.parse(originalSql);
// 将解析之后的SQL信息,封装到StaticSqlSource对象中
return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
}
这里好像已经到了挺核心的代码了,首先创建参数映射的分词Handler,然后创建分词解析器,最后通过分词解析器去解析#{},最后将解析之后的SQL信息,封装到StaticSqlSource对象中。
说到了这里,可能有人想那为什么DynamicSql就这么简单的结束了,我只能说先不要急,精彩的还在后面。到这里貌似已经动态SQL解析貌似结束了,可是我们貌似忽略了ParameterMappingTokenHandler中的handleToken(String content)和buildParameterMapping方法,因为我这里没贴parser.parse(originalSql)的parse方法代码,对于handleToken的方法就是在这个方法中调用的,然后在handleToken中调用buildParameterMapping。这块读者可以自己去看看。
最后来看下前一篇没说的,就是创建MappedStatement对象。这个方法的入口和createSqlSource方法在同一个方法里,我们直接来看下是如何创建的,代码如下:
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class parameterType,
String resultMap,
Class resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
//利用构建者模式,去创建MappedStatement.Builder,用于创建MappedStatement对象
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 通过MappedStatement.Builder,构建一个MappedStatement
MappedStatement statement = statementBuilder.build();
// 将MappedStatement对象存储到Configuration中的Map集合中,key为statement的id,value为MappedStatement对象
configuration.addMappedStatement(statement);
return statement;
}
看上去很长的一段代码,其实不过是唬人的,这段代码中核心点在于首先利用构建者模式,去创建MappedStatement.Builder对象,最后通过MappedStatement.Builder,构建一个MappedStatement,其它的都是辅助作用而已。
至于MappedStatement.Builder(……)的代码大家可以自己去看,代码很简单,对于statementBuilder.build()的代码也很简单,在这里就不贴代码了,感兴趣的可以自己看看。
其实到这里就已经结束本篇文章了,笔者由于阅历有限,可能遗漏很多细节,大家可以给笔者留言,谢谢!那就最后来个总结!