Mybatis系列四:XMLMapperBuilder解析XL配置文件

概述

《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;

 }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值