上面方法的逻辑我前面已经说过,主要是用来判断节点是否包含一些动态标记,比如 ${} 占位符以及动态 SQL 节点等。这里,不管是动态 SQL 节点还是静态 SQL 节点,我们都可以把它们看成是 SQL 片段,一个 SQL 语句由多个 SQL 片段组成。在解析过程中,这些 SQL 片段被存储在 contents 集合中。最后,该集合会被传给 MixedSqlNode 构造方法,用于创建 MixedSqlNode 实例。从 MixedSqlNode 类名上可知,它会存储多种类型的 SqlNode。除了上面代码中已出现的几种 SqlNode 实现类,还有一些 SqlNode 实现类未出现在上面的代码中。但它们也参与了 SQL 语句节点的解析过程,这里我们来看一下这些幕后的 SqlNode 类。
上面的 SqlNode 实现类用于处理不同的动态 SQL 逻辑,这些 SqlNode 是如何生成的呢?答案是由各种 NodeHandler 生成。我们再回到上面的代码中,可以看到这样一句代码:
handler.handleNode(child, contents);该代码用于处理动态 SQL 节点,并生成相应的 SqlNode。下面来简单分析一下 WhereHandler 的代码。
/** 定义在 XMLScriptBuilder 中 */ private class WhereHandler implements NodeHandler { public WhereHandler() { } @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { // 调用 parseDynamicTags 解析 <where> 节点 MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); // 创建 WhereSqlNode WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode); // 添加到 targetContents targetContents.add(where); } }如上,handleNode 方法内部会再次调用 parseDynamicTags 解析 <where> 节点中的内容,这样又会生成一个 MixedSqlNode 对象。最终,整个 SQL 语句节点会生成一个具有树状结构的 MixedSqlNode。如下图:
到此,SQL 语句的解析过程就分析完了。现在,我们已经将 XML 配置解析了 SqlSource,但这还没有结束。SqlSource 中只能记录 SQL 语句信息,除此之外,这里还有一些额外的信息需要记录。因此,我们需要一个类能够同时存储 SqlSource 和其他的信息。这个类就是 MappedStatement。下面我们来看一下它的构建过程。
2.1.5.4 构建 MappedStatementSQL 语句节点可以定义很多属性,这些属性和属性值最终存储在 MappedStatement 中。下面我们看一下 MappedStatement 的构建过程是怎样的。
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 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)) .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) .resultSetType(resultSetType).useCache(valueOrDefault(useCache, isSelect)) .cache(currentCache); // 获取或创建 ParameterMap ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } // 构建 MappedStatement,没有什么复杂逻辑,不跟下去了 MappedStatement statement = statementBuilder.build(); // 添加 MappedStatement 到 configuration 的 mappedStatements 集合中 configuration.addMappedStatement(statement); return statement; }上面就是 MappedStatement,没什么复杂的地方,就不多说了。
2.1.6 小节本章分析了映射文件的解析过程,总的来说,本章的内容还是比较复杂的,逻辑太多。不过如果大家自己也能把映射文件的解析过程认真分析一遍,会对 MyBatis 有更深入的理解。分析过程很累,但是在此过程中会收获了很多东西,还是很开心的。好了,本章内容先到这里。后面还有一些代码需要分析,我们继续往后看。
2.2 Mapper 接口绑定过程分析