MyBatis源码简读——2.1.2 mapper映射文件解析
映射文件的解析入口是 org.apache.ibatis.builder.xml.XMLMapperBuilder
其主要方法和属性
属性
首先简单介绍下几个属性的作用
- builderAssistant,一个mapper构造器的工具
- parser:基于java XPath的解析器
- resource:资源地址
- sqlFragments:是一个map,大概意思是一个可以被其他语句引用的重用语句块的集合
parse
parse是其主要的Mapper解析逻辑
// 解析Mapper XML
public void parse() {
// 判断当前mapper是否已经加载过了
if (!configuration.isResourceLoaded(resource)) {
// 解析mapper节点
configurationElement(parser.evalNode("/mapper"));
// 标记该Mapper已经加载过
configuration.addLoadedResource(resource);
// 绑定Mapper
bindMapperForNamespace();
}
// 解析待定的 <resultMap /> 节点
parsePendingResultMaps();
// 解析待定的 <cache-ref /> 节点
parsePendingCacheRefs();
// 解析待定的 SQL 语句的节点
parsePendingStatements();
}
// 解析 <mapper /> 节点
private void configurationElement(XNode context) {
try {
// 获得命名空间属性属性的值
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析cache-ref
cacheRefElement(context.evalNode("cache-ref"));
// 解析cache
cacheElement(context.evalNode("cache"));
// 已废弃!老式风格的参数映射。内联参数是首选,这个元素可能在将来被移除,这里不会记录。
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 <resultMap /> 节点们
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析 <sql /> 节点们
sqlElement(context.evalNodes("/mapper/sql"));
// 解析 <select /> <insert /> <update /> <delete /> 节点们
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
其大概的内容,就和注释上说的,从上往下依次解析mapper。然后解析器内部的各个标签。
其实每个方法相对独立,读起来也不算太难,这里就简单看部分方法:
-
resultMapElements
-
sqlElement
-
buildStatementFromContext
首先我们看到这三个方法,都是从context中取出指定位置的XNode数据。分别对应mapper文件下的结果集合,SQL,CRUD标签
resultMapElements
其最终调用的方法是
// 解析 <resultMap /> 节点
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
// 获得类型属性
String type = resultMapNode.getStringAttribute("type",
// 获得type属性
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
// 解析 type 对应的类
Class<?> typeClass = resolveClass(type);
if (typeClass == null) {
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
}
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<>();
resultMappings.addAll(additionalResultMappings);
// 遍历 <resultMap /> 的子节点
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
// 处理 <constructor /> 节点
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {// 处理 <discriminator /> 节点
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {// 处理其它节点
List<ResultFlag> flags = new ArrayList<>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
// 获得ID属性
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 获得extends属性
String extend = resultMapNode.getStringAttribute("extends");
// 获得autoMapping属性
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
// 创建 ResultMapResolver 对象,执行解析
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
其内部逻辑就是,
首先获得resultMap中的type,id,extends,autoMapping等属性,并循环resultMap内部子节点存在的节点并构建成ResultMapping。
然后通过这些参数创建ResultMapResolver
// 创建 ResultMapResolver 对象,执行解析
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
// org.apache.ibatis.builder.ResultMapResolver
public class ResultMapResolver {
private final MapperBuilderAssistant assistant;
// mapper 标识
private final String id;
// 类型
private final Class<?> type;
// 继承自哪个 ResultMap
private final String extend;
// Discriminator 对象
private final Discriminator discriminator;
// ResultMapping 集合
private final List<ResultMapping> resultMappings;
// 是否自动匹配
private final Boolean autoMapping;
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
this.assistant = assistant;
this.id = id;
this.type = type;
this.extend = extend;
this.discriminator = discriminator;
this.resultMappings = resultMappings;
this.autoMapping = autoMapping;
}
public ResultMap resolve() {
return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
}
而最终调用的是XMLMapperBuilder中的builderAssistant的方法
// 创建 ResultMap 对象,并添加到 Configuration 中
public ResultMap addResultMap(
String id,
Class<?> type,
String extend,
Discriminator discriminator,
List<ResultMapping> resultMappings,
Boolean autoMapping) {
// 获得 ResultMap 编号,即格式为 `${namespace}.${id}`
id = applyCurrentNamespace(id, false);
// 获取完整的 extend 属性,即格式为 `${namespace}.${extend}` 。从这里的逻辑来看,貌似只能自己 namespace 下的 ResultMap 。
extend = applyCurrentNamespace(extend, true);
// 如果有父类,则将父类的 ResultMap 集合,添加到 resultMappings 中
if (extend != null) {
if (!configuration.hasResultMap(extend)) {
throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
}
ResultMap resultMap = configuration.getResultMap(extend);
// 获取 extend 的 ResultMap 对象的 ResultMapping 集合,并移除 resultMappings
List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
extendedResultMappings.removeAll(resultMappings);
// Remove parent constructor if this resultMap declares a constructor.
// 判断当前的 resultMappings 是否有构造方法,
// 如果有,则从 extendedResultMappings 移除所有的构造类型的 ResultMapping 们
boolean declaresConstructor = false;
for (ResultMapping resultMapping : resultMappings) {
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
declaresConstructor = true;
break;
}
}
if (declaresConstructor) {
extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));
}
// 将 extendedResultMappings 添加到 resultMappings 中
resultMappings.addAll(extendedResultMappings);
}
// 创建 ResultMap 对象
ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
.discriminator(discriminator)
.build();
// 添加到 configuration 中
configuration.addResultMap(resultMap);
return resultMap;
}
被解析后的对象会被封装成ResultMap保存在Configuration中,而且主键为${namespace}.${id}
ps.在ResultMap的build方法中,会将行映射ResultMapping进行拆分,拆分成不同的集合进行保存。
sqlElement
这一个方法是对SQL的解析,最终的逻辑是
// org\apache\ibatis\builder\xml\XMLMapperBuilder.java
private void sqlElement(List<XNode> list, String requiredDatabaseId) {
// 遍历所有 <sql /> 节点
for (XNode context : list) {
// 获得数据源标识符参数
String databaseId = context.getStringAttribute("databaseId");
// 获得ID,并且进行拼装
String id = context.getStringAttribute("id");
id = builderAssistant.applyCurrentNamespace(id, false);
// 判断 databaseId 是否匹配
// 因为 sqlFragments 是来自 Configuration 的 sqlFragments 属性,所以相当于也被添加了
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
// 添加到 sqlFragments 中
sqlFragments.put(id, context);
}
}
}
最终会将SQL以 key:id值,value:XNode的格式保存起来
buildStatementFromContext
这是对最终的CRUD语句进行解析的方法。类似resultMapElements的解析,最后
// org/apache/ibatis/builder/xml/XMLStatementBuilder.java
public void parseStatementNode() {
// 获得 id 属性,编号。
String id = context.getStringAttribute("id");
// 获得 databaseId , 判断 databaseId 是否匹配
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
// 解析各种属性
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// 解析参数类别,并获得类
String parameterType = context.getStringAttribute("parameterType");
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获得 lang 对应的 LanguageDriver 对象
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
// 解析 <selectKey /> 标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
KeyGenerator keyGenerator;
// 优先,从 configuration 中获得 KeyGenerator 对象。如果存在,意味着是 <selectKey /> 标签配置的
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 获得各种属性
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class<?> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 创建 MappedStatement 对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
将从XML中解析的各种参数resultMap等,作为属性值通过builderAssistant进行创建操作。而且更深一层的逻辑是
// 构建 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) {
// 如果 Cache 未解析,抛出 IncompleteElementException 异常
if (unresolvedCacheRef) {
throw new IncompleteElementException("Cache-ref not yet resolved");
}
// 获得 id 编号,格式为 `${namespace}.${id}`
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 创建 MappedStatement.Builder 对象
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 ,并设置到 MappedStatement.Builder
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
// 创建 MappedStatement 对象
MappedStatement statement = statementBuilder.build();
// 添加到 configuration 中
configuration.addMappedStatement(statement);
return statement;
}
构建出一个MappedStatement对象,最终将此对象放入configuration中。
后续逻辑
bindMapperForNamespace();
将已经进行处理过的mapper添加进配置中,防止后续逻辑再次添加
parsePendingXXX
这系列的方法,主要逻辑就是从配置中找到没有被解析的内容,尝试使用此mapper的MapperBuilderAssistant进行解析,假如解析成功则从待定区移除掉,否则继续等待下一个mapper。
MapperBuilderAssistant
简单的说一下这个方法, 这是一个mapper的解析工具,他负责将解析的各种数据进行封装然后保存进Configuration中,同时他也取出很多Configuration的数据进行操作。
这是一个很有用的方法,当目前简读的过程中并不准备介绍的过于详细,自己学习的时候添加注解笔记的代码可以在这里看到
https://gitee.com/daifylearn/mybatis-3
ps.发一个别人的图,看看这个图结合上面代码大概就明白,mapper解析到底解析什么,解析成了什么。