概述
在《Mybatis系列一:构建SqlSessionFactory》一文中了解了Mybatis配置文件的解析,其中对mappers元素解析使用到了XMLMapperBuilder这个类,现在分析下这个类是如何解析Mapper配置文件的。当然也调用了Configuration#addMappers方法,实际上调用的是MapperRegistry#addMappers方法。
<mappers>
// XMLMapperBuilder解析Mapper.xml配置文件
<mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
<mapper url="file:///var/mappers/AuthorMapper.xml"/>
// MapperAnnotationBuilder解析Mapper接口,调用XMLMapperBuilder解析Mapper.xml配置文件
<mapper class="org.mybatis.builder.AuthorMapper"/>
<package name="org.mybatis.builder"/>
</mappers>
Mapper.xml配置文件中的select|insert|update|delete元素解析成MappedStatement对象,保存Configuration#mappedStatements中。
Mapper.class注册到MapperRegistry#knownMappers中,映射一个MapperProxyFactory,为Mapper创建代理对象。
一、XMLMapperBuilder
1.1 构造器
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);
// Mapper构造助手
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
// XML sql片段
this.sqlFragments = sqlFragments;
this.resource = resource;
}
1.2 解析Mapper文件
public void parse() {
// 如果Mapper.xml文件未解析则解析
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
// 解析cache-ref元素时,因用的缓存所在命名空间Mapper.xml文件还没有解析导致解析cache-ref元素以及select|insert|update|delete元素(使用到了cache-ref)失败。ResultMap通过设置extends属性继承某个ResultMap,而继承的ResultMap还没有解析,导致当前ResultMap解析失败。全部解析完之后,需要针对因依赖而解析失败的再次尝试解析。
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
1.3 解析Mapper元素 configurationElement
private void configurationElement(XNode context) {
try {
// 必须指定Mapper元素的namespace属性
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"));
// 解析parameterMap元素
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);
}
}
1.3.1 解析cache-ref元素
解析cache-ref元素时,因用的缓存所在命名空间Mapper.xml文件还没有解析导致解析cache-ref元素失败。全部解析完之后,需要针对因依赖而解析失败的再次尝试解析。
private void cacheRefElement(XNode context) {
if (context != null) {
// 记录当前命名空间引用其它命名空间的缓存引用
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
// Configuration#caches保存命名空间和缓存的映射关系
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
// 解析引用的其它命名空间缓存,如果不存在会抛异常
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
// 记录不存在的引用缓存,此时可能因用的命名空间所在Mapper还没解析,所以获取不到
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
1.3.2 解析cache元素
Configuration实例化角色时注册了五种缓存类型:PERPETUAL、FIFO、LRU、SOFT、WEAK。
private void cacheElement(XNode context) throws Exception {
if (context != null) {
// 获取缓存类型,默认是PERPETUAL(永久的)
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
// 创建Cache实例,保存到Configuration#caches中
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
1.3.2 解析parameterMap元素
parameterMap标记弃用,这是引用外部 parameterMap 的已经被废弃的方法。请使用内联参数映射和 parameterType 属性。
private void parameterMapElement(List<XNode> list) throws Exception {
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class<?> parameterClass = resolveClass(type);
List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap = parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler = parameterNode.getStringAttribute("typeHandler");
// 数字精度
Integer numericScale = parameterNode.getIntAttribute("numericScale");
// IN, OUT, INOUT
ParameterMode modeEnum = resolveParameterMode(mode);
Class<?> javaTypeClass = resolveClass(javaType);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
// 创建ParameterMap实例,保存到Configuration#parameterMaps中
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}
1.3.4 解析resultMap元素(比较复杂)
解析select|insert|update|delete元素使用到了cache-ref,因用的缓存所在命名空间Mapper.xml文件还没有解析导致解析select|insert|update|delete元素失败。全部解析完之后,需要针对因依赖而解析失败的再次尝试解析。
1.3.4.1 解析resultMap元素 resultMapElements
private void resultMapElements(List<XNode> list) throws Exception {
for (XNode resultMapNode : list) {
try {
resultMapElement(resultMapNode);
} catch (IncompleteElementException e) {
// ignore, it will be retried
}
}
}
1.3.4.2 解析resultMap元素 resultMapElement
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
}
1.3.4.3 解析resultMap元素 resultMapElement
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
String id = resultMapNode.getStringAttribute("id",
resultMapNode.getValueBasedIdentifier());
// 获取ResultMap类型
String type = resultMapNode.getStringAttribute("type",
resultMapNode.getStringAttribute("ofType",
resultMapNode.getStringAttribute("resultType",
resultMapNode.getStringAttribute("javaType"))));
String extend = resultMapNode.getStringAttribute("extends");
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
Class<?> typeClass = resolveClass(type);
Discriminator discriminator = null;
List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
resultMappings.addAll(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren();
for (XNode resultChild : resultChildren) {
// 构造器参数
if ("constructor".equals(resultChild.getName())) {
processConstructorElement(resultChild, typeClass, resultMappings);
} else if ("discriminator".equals(resultChild.getName())) {
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
} else {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
if ("id".equals(resultChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
}
}
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
try {
// 创建ResultMap实例,保存到resultMaps中
return resultMapResolver.resolve();
} catch (IncompleteElementException e) {
configuration.addIncompleteResultMap(resultMapResolver);
throw e;
}
}
1.3.4.4 处理构造器参数元素 processConstructorElement
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
List<XNode> argChildren = resultChild.getChildren();
for (XNode argChild : argChildren) {
List<ResultFlag> flags = new ArrayList<ResultFlag>();
flags.add(ResultFlag.CONSTRUCTOR);
if ("idArg".equals(argChild.getName())) {
flags.add(ResultFlag.ID);
}
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
}
}
1.3.4.5 构造结果映射元素 buildResultMappingFromContext
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
String property;
if (flags.contains(ResultFlag.CONSTRUCTOR)) {
property = context.getStringAttribute("name");
} else {
property = context.getStringAttribute("property");
}
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String nestedSelect = context.getStringAttribute("select");
String nestedResultMap = context.getStringAttribute("resultMap",
processNestedResultMappings(context, Collections.<ResultMapping> emptyList()));
String notNullColumn = context.getStringAttribute("notNullColumn");
String columnPrefix = context.getStringAttribute("columnPrefix");
String typeHandler = context.getStringAttribute("typeHandler");
String resultSet = context.getStringAttribute("resultSet");
String foreignColumn = context.getStringAttribute("foreignColumn");
boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}
1.3.4.6 处理元素 processDiscriminatorElement
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
String column = context.getStringAttribute("column");
String javaType = context.getStringAttribute("javaType");
String jdbcType = context.getStringAttribute("jdbcType");
String typeHandler = context.getStringAttribute("typeHandler");
Class<?> javaTypeClass = resolveClass(javaType);
@SuppressWarnings("unchecked")
Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Map<String, String> discriminatorMap = new HashMap<String, String>();
for (XNode caseChild : context.getChildren()) {
String value = caseChild.getStringAttribute("value");
String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
discriminatorMap.put(value, resultMap);
}
return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
}
1.3.4.7 处理内嵌结果映射元素 processNestedResultMappings
private String processNestedResultMappings(XNode context, List<ResultMapping> resultMappings) throws Exception {
if ("association".equals(context.getName())
|| "collection".equals(context.getName())
|| "case".equals(context.getName())) {
if (context.getStringAttribute("select") == null) {
ResultMap resultMap = resultMapElement(context, resultMappings);
return resultMap.getId();
}
}
return null;
}
1.3.5 解析sql元素
private void sqlElement(List<XNode> list) throws Exception {
if (configuration.getDatabaseId() != null) {
sqlElement(list, configuration.getDatabaseId());
}
sqlElement(list, null);
}
private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
for (XNode context : list) {
String databaseId = context.getStringAttribute("databaseId");
String id = context.getStringAttribute("id");
// 添加当前命名空间前缀
id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context);
}
}
}
// SQL片段是否匹配指定数据库。
// 如果指定适用数据库,SQL片段未指定适用数据库或者匹配则加载,指定的不匹配不加载
// 如果未指定适用数据库,SQL片段指定适用数据库则不加载,
private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
if (requiredDatabaseId != null) {
if (!requiredDatabaseId.equals(databaseId)) {
return false;
}
} else {
if (databaseId != null) {
return false;
}
// 已经加载的SQL片段指定了databaseId,则当前未指定databaseId的SQL片段忽略
if (this.sqlFragments.containsKey(id)) {
XNode context = this.sqlFragments.get(id);
if (context.getStringAttribute("databaseId") != null) {
return false;
}
}
}
return true;
}
1.3.6 解析select|insert|update|delete元素
ResultMap通过设置extends属性继承某个ResultMap,而继承的ResultMap还没有解析,导致当前ResultMap解析失败。全部解析完之后,需要针对因依赖而解析失败的再次尝试解析。传送门《Mybatis系列七:XMLStatementBuilder创建MappedStatement》
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
// 创建MappedStatement实例,保存到Configuration#mappedStatements中
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
1.4 解析命名空间为Mapper并注册 bindMapperForNamespace
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
// Mapper接口注册到Configuration#MapperRegistry中,
configuration.addMapper(boundType);
}
}
}
}
1.5 解析待处理ResultMap parsePendingResultMaps
private void parsePendingResultMaps() {
Collection<ResultMapResolver> incompleteResultMaps = configuration.getIncompleteResultMaps();
synchronized (incompleteResultMaps) {
Iterator<ResultMapResolver> iter = incompleteResultMaps.iterator();
while (iter.hasNext()) {
try {
iter.next().resolve();
iter.remove();
} catch (IncompleteElementException e) {
// ResultMap is still missing a resource...
}
}
}
}
1.6 解析待处理CacheRef parsePendingCacheRefs
private void parsePendingCacheRefs() {
Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
synchronized (incompleteCacheRefs) {
Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
while (iter.hasNext()) {
try {
iter.next().resolveCacheRef();
iter.remove();
} catch (IncompleteElementException e) {
// Cache ref is still missing a resource...
}
}
}
}
1.7 解析待处理MappedStatement parsePendingStatements
private void parsePendingStatements() {
Collection<XMLStatementBuilder> incompleteStatements = configuration.getIncompleteStatements();
synchronized (incompleteStatements) {
Iterator<XMLStatementBuilder> iter = incompleteStatements.iterator();
while (iter.hasNext()) {
try {
iter.next().parseStatementNode();
iter.remove();
} catch (IncompleteElementException e) {
// Statement is still missing a resource...
}
}
}
}
二、CacheRefResolver
// 封装MapperBuilderAssistant和cacheRefNamespace
public class CacheRefResolver {
private final MapperBuilderAssistant assistant;
private final String cacheRefNamespace;
public CacheRefResolver(MapperBuilderAssistant assistant, String cacheRefNamespace) {
this.assistant = assistant;
this.cacheRefNamespace = cacheRefNamespace;
}
public Cache resolveCacheRef() {
// 传送门3.1
return assistant.useCacheRef(cacheRefNamespace);
}
}
三、MapperBuilderAssistant
3.1 获取指定命名空间的缓存 useCacheRef
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
// Configuration#caches保存命名空间和缓存的映射关系。
Cache cache = configuration.getCache(namespace);
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
currentCache = cache;
// 引用其它命名空间已解析的缓存
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
3.2 创建命名空间使用的缓存 useNewCache
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
// 命名空间作为缓存的ID,将创建的缓存保存到Configuration#caches中,其它命名空间可以使用
configuration.addCache(cache);
currentCache = cache;
return cache;
}
3.3 创建输入参数映射 ParameterMapping
public ParameterMapping buildParameterMapping(
Class<?> parameterType,
String property,
Class<?> javaType,
JdbcType jdbcType,
String resultMap,
ParameterMode parameterMode,
Class<? extends TypeHandler<?>> typeHandler,
Integer numericScale) {
resultMap = applyCurrentNamespace(resultMap, true);
// 解析Java类型:指定JavaType就用指定的。未指定如果JdbcType是JdbcType.CURSOR、Map则可以获取到。否则解析parameterType元数据,根据property属性getter方法返回值获取JavaType。
Class<?> javaTypeClass = resolveParameterJavaType(parameterType, property, javaType, jdbcType);
// 获取类型处理器实例
TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
return new ParameterMapping.Builder(configuration, property, javaTypeClass)
.jdbcType(jdbcType)
.resultMapId(resultMap)
.mode(parameterMode)
.numericScale(numericScale)
.typeHandler(typeHandlerInstance)
.build();
}
3.4 添加当前命名空间前缀 applyCurrentNamespace
// 添加当前命名空间前缀
public String applyCurrentNamespace(String base, boolean isReference) {
if (base == null) {
return null;
}
if (isReference) {
// is it qualified with any namespace yet?
if (base.contains(".")) {
return base;
}
} else {
// is it qualified with this namespace yet?
if (base.startsWith(currentNamespace + ".")) {
return base;
}
if (base.contains(".")) {
throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
}
}
return currentNamespace + "." + base;
}