前言

配置示例

  • 使用相对于类路径的资源引用
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
  • 使用完全限定资源定位符(URL)
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
  • 使用映射器接口实现类的完全限定类名
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
  • 将包内的映射器接口实现全部注册为映射器
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

mapper 文件解析

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
            // 将包内的映射器接口实现全部注册为映射器 
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            // 使用相对于类路径的资源引用
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            // 使用完全限定资源定位符(URL)
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
              // 使用映射器接口实现类的完全限定类名
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

按 XML 映射文件解析

XMLMapperBuilderparse 方法执行 mapper 接口映射文件的解析

public void parse() {
    // 判断 resource 文件是否加载过
    if (!configuration.isResourceLoaded(resource)) {
      // 解析 mapper 文件
      configurationElement(parser.evalNode("/mapper"));
      // 将 resource 文件标记为已加载
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
private void configurationElement(XNode context) {
    try {
      // 获取 mapper 的命名空间
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 解析 cache-ref
      // 从 configuration 中通过参照的缓存的命名空间获取缓存并复制到当前 mapper
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析 cache
      // 创建 cache 实例并注册到 configuration 的 caches 容器中
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析 resultMap 标签
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      // 解析 sql 语句标签
      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);
    }
  }

cache-ref 解析

通过配置 cache-ref 引用其他命名空间的缓存配置实例

private void cacheRefElement(XNode context) {
    if (context != null) {
      // 将当前 mapper 命名空间和参照缓存的命名空间进行绑定, 注册到 configuration 的 cacheRefMap
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        // 获取参照缓存 并绑定到当前 mapper
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
  }

ConfigurationaddCacheRef 方法如下:

public void addCacheRef(String namespace, String referencedNamespace) {
    cacheRefMap.put(namespace, referencedNamespace);
  }

从上述代码发现,解析 cache-ref 标签之后会将当前命名空间和缓存参照的命名空间进行绑定注册到 cacheRefMap 容器中。

public Cache resolveCacheRef() {
    // 从参照的命名空间获取 cache 并复制到当前 mapper
    return assistant.useCacheRef(cacheRefNamespace);
  }
public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      // 获取参照的缓存实例
      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);
    }
  }

resolveCacheRef 的操作来看,在解析参照缓存的命名空间之后,会从 configurationcaches 缓存容器中获取参照缓存实例,若存在参照缓存则将其复制到当前命名空间下,反之抛出异常。

cache 解析

private void cacheElement(XNode context) {
    if (context != null) {
      // 获取缓存的实现类,默认为 PERPETUAL,也就是 PerpetualCache 类
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);

      // 获取缓存的回收策略,默认为 LRU 策略,也就是 LruCache
      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();
      // 创建缓存对象
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }
public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 设置缓存命名空间,也就是 cache 的 id
    // 设置缓存的实现类, 默认为 PerpetualCache
    // 设置缓存的回收策略,默认为 LruCache
    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();
    // 将 cache 添加到
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

解析 cache 的流程很简单,其过程如下:

  • 获取缓存的实现类 type 属性,默认为 PerpetualCache
  • 获取缓存的回收策略 eviction 属性, 默认为 LRU 策略
  • 获取缓存刷新时间,size 等熟悉
  • 通过 builderAssistant (mapper 构造助手) 创建 cache 实例
  • 将 cache 实例注册到 configurationcaches 容器中

resultMap 解析

  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取映射的类
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    // 遍历 resultMap 标签下的子标签 常用的为 id, result 标签
    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<>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        // ResultMapping 存储的是 标签 result 的内容 包括java bean 属性与 db 列的映射关系
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 获取 resultMap 的 id
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    String extend = resultMapNode.getStringAttribute("extends");
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");

    // ResultMap 存储的是 标签 resultMap 的内容
    // 其包括了 ResultMapping 集合,二者一对多的关系
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

按 mapper 接口解析



# Mybatis  

tocToc: