在前一篇文章深入浅出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()的代码也很简单,在这里就不贴代码了,感兴趣的可以自己看看。
其实到这里就已经结束本篇文章了,笔者由于阅历有限,可能遗漏很多细节,大家可以给笔者留言,谢谢!那就最后来个总结!