mybatis源码分析:mapper解析过程

Scroll Down

1、概述

解析mapper,mybatis中提供了四种方式来注册mapper。
(1)、采用package的方式,这种方式因为class已经提供了,直接使用Configuration的添加mapper方式来注册。
(2)、采用resource方式,这种方式一般是用户提供一个mapper.xml的配置文件。然后使用XMLMapperBuilder来解析。
(3)、采用url方式,这种方式与resources的方式一致。
(4)、采用class的方式,这种方式与package是一致的。

特别注意,mybatis一般只会使用class和xml文件其中一种方式来注册。
XMLMapperBuilder是用来解析mapper.XML文件。XMLMapperBuilder解析完XML文件后还需要调用MapperAnnotationBuilder来处理incomplete的方法。
MapperAnnotationBuilder是用来解析Annotation mapper的。

2、XMLMapperBuilder的整体解析过程

解析mapper的过程一般分为两类:
一类是指定资源,比如resource或者url,这种一般是用户提供了xml文件,此时需要解析XML文件,然后注册mapper。
另一类是指定class类型,比如package或者class,这种一般是用户采用了注解的方式,此时需要解析class,然后注册mapper。
而mapper的解析的门面是MapperAnnotationBuilder这个类,该类两种方式都提供了解析,也可以直接调用XMLMapperBuilder的parse解析。
XMLMapperBuilder的类字段如下:

// 使用XPathParser解析XML文件
private final XPathParser parser;
// 持有MapperBuilder的引用,主要的作用是用来注册解析的mapper
private final MapperBuilderAssistant builderAssistant;
// 缓存解析的XNode
private final Map<String, XNode> sqlFragments;
// 资源名
private final String resource;

先来说明下XMLMapperBuilder中的元素是如何注入到Configuration中的。
mybatisconfigmapper 1.png
XMLMapperBuilder类主要是用于解析mapper文件。
这里重点讲下MapperRegistry的注册mapper逻辑:

// 解析只提供包名的情况
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 获取包名下的全部类
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // 循环注册扫描到的class
      addMapper(mapperClass);
    }
}

public <T> void addMapper(Class<T> type) {
    // 如果不是接口,则不注册
    if (type.isInterface()) {
      // 如果已经注册过了,则抛出异常
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 注册mapper,这个mapper是通过JDK动态代理MapperProxyFactory来操作的。
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // 解析Mapper接口中的注解信息,并注册到Configuration中
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
}

上面使用了MapperProxyFactory,这里创建生成动态代理工厂。后续会创建一个关于接口的动态代理类。
MapperAnnotationBuilder的parse方法解析代码如下:

public void parse() {
    String resource = type.toString();
    // 如果类型没有被解析过
    if (!configuration.isResourceLoaded(resource)) {
      // 解析对于类型名的xml文件
      loadXmlResource();
      // 缓存已经解析过的类型
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());
      parseCache();
      parseCacheRef();
      // 解析存在注解的情况@Select|@Update|@Insert|@Delete
      for (Method method : type.getMethods()) {
        if (!canHaveStatement(method)) {
          continue;
        }
        if (getSqlCommandType(method) == SqlCommandType.SELECT && method.getAnnotation(ResultMap.class) == null) {
          // 解析查询语句
          parseResultMap(method);
        }
        try {
          // 解析其他类型语句
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 解析还没有解析完成的方法
    parsePendingMethods();
}

首先来看下解析mapper XML文件逻辑:

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      // #1347
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {
        // Search XML mapper that is not in the module but in the classpath.
        try {
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e2) {
          // ignore, resource is not required
        }
      }
      if (inputStream != null) {
        // 使用XMLMapperBuilder来解析Mapper.xml文件
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
}

解析Mapper.xml文件是使用XMLMapperBuilder类的parse来完成的。
先来看下XMLMapperBuilder这个类:
XMLMapperBuilder这个类是用来解析mapper.xml文件内容,并将文件内容转化成对应的数据结构的。

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 解析mapper.xml文件中mapper的节点内容
      configurationElement(parser.evalNode("/mapper"));
      // 缓存已经解析内容
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    // 解析构建ResultMap
    parsePendingResultMaps();
    // 解析构建cache
    parsePendingCacheRefs();
    // 解析构建select|insert|update|delete
    parsePendingStatements();
}

3、解析mapper.xml文件中mapper的节点内容

解析mapper节点是通过configurationElement方法来完成的。代码如下:

private void configurationElement(XNode context) {
    try {
      // 首先获取mapper标签的namespace属性
      String namespace = context.getStringAttribute("namespace");
      // 如果namespace属性为空,则抛出BuilderException异常
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // 在MapperBuilder中标记当前的namespace
      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);
    }
}

3.1、解析cache-ref标签

这里所说的cache就是mybatis中的二级缓存,相较于一级缓存(SqlSession)二级缓存是一种针对于某个名称空间的缓存cache,而cache-ref标签主要是用来引用其他名称空间的缓存。
cache-ref原理如下:
mybatismappercache.png
代码如下:

private void cacheRefElement(XNode context) {
    if (context != null) {
      // 在Configuration中设置namespace属性
      configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
      // 构建CacheRef解析器
      CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
      try {
        // 调用CacheRef解析器的resolveCacheRef方法来解析,这里第一次是解析不到的,会抛出IncompleteElementException异常
        cacheRefResolver.resolveCacheRef();
      } catch (IncompleteElementException e) {
        // 如果捕获了IncompleteElementException异常,就将这个cache放到还未解析完成的CacheRef中。
        configuration.addIncompleteCacheRef(cacheRefResolver);
      }
    }
}

CacheRefResolver中的resolveCacheRef方法如下:

public Cache resolveCacheRef() {
    // 调用的是MapperBuilderAssistant的useCacheRef方法。
    return assistant.useCacheRef(cacheRefNamespace);
}

MapperBuilderAssistant的useCacheRef方法如下:

public Cache useCacheRef(String namespace) {
    if (namespace == null) {
      throw new BuilderException("cache-ref element requires a namespace attribute.");
    }
    try {
      unresolvedCacheRef = true;
      // 从Configuration中获取Cache
      Cache cache = configuration.getCache(namespace);
      // 如果此时获取不到,那么就抛出IncompleteElementException异常,等待所有Mapper的Cache标签被解析完成之后再获取。
      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);
    }
}

这里有个疑问为什么不把cache-ref标签的解析放到cache的后面呢。其实如果放到cache标签后面也是一样的,因为cache-ref标签的namespace可以指向其他mapper,此时还是会有没有解析完cache的mapper。所以这里与顺序无关。

3.2、解析cache标签

cache标签是用来支持Mybatis的二级缓存功能的。
举个例子:

<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

cache标签是通过XMLConfigBuilder的cacheElement方法来实现的。

private void cacheElement(XNode context) {
    if (context != null) {
      // 首先获取cache标签的type属性,如果type属性不存在,默认是PERPETUAL,也就是使用PerpetualCache。
      String type = context.getStringAttribute("type", "PERPETUAL");
      // 获取Cache的类型
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      // 获取eviction属性,缺省情况下为LRU。
      String eviction = context.getStringAttribute("eviction", "LRU");
      // 获取eviction的类型
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      // 获取flushInterval属性
      Long flushInterval = context.getLongAttribute("flushInterval");
      // 获取size属性
      Integer size = context.getIntAttribute("size");
      // 获取readOnly属性,默认为false
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      // 获取blocking属性,默认为false
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 获取cache标签下面全部的子标签,并封装成properties
      Properties props = context.getChildrenAsProperties();
      // 创建Cache对象,并注册到Configuration中
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
}

builderAssistant的useNewCache方法如下:

// 通过构造器模式创建Cache
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();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
}

3.3、解析resultMap标签

resultMap是Mybatis将JDBC类型数据转换为Java Bean对象的映射关系类。
它的数据结构如下所示:

// 持有Configuration属性
private Configuration configuration;
// ResultMap标签的id
private String id;
// ResultMap标签的type属性
private Class<?> type;
// 记录除discriminator节点之外的其他映射关系
private List<ResultMapping> resultMappings;
// 记录映射关系中带有ID标志的映射关系
private List<ResultMapping> idResultMappings;
// 记录映射关系中带有constructor标志的映射关系
private List<ResultMapping> constructorResultMappings;
// 记录映射关系中不带有constructor的标志的映射关系
private List<ResultMapping> propertyResultMappings;
// 记录所有映射关系中涉及column属性集合
private Set<String> mappedColumns;
// 记录所有映射关系中property属性集合
private Set<String> mappedProperties;
// 鉴别器
private Discriminator discriminator;
// 是否存在resultMap嵌套的情况
private boolean hasNestedResultMaps;
// 是否存在嵌套查询的情况
private boolean hasNestedQueries;
// 是否自动映射,如果将属性设置为true,则启动自动映射功能
// 即自动查找与列名相同名称的属性,通过setter方法赋值,如果为false则需要手动注名映射关系才可以调用setter方法赋值。
private Boolean autoMapping;

ResultMapping对象字段如下:

// 持有Configuration对象
private Configuration configuration;
// resultMap子节点的property属性值
private String property;
// resultMap子节点的column属性值
private String column;
// resultMap子节点的javaType属性
private Class<?> javaType;
// resultMap子节点的jdbcType属性
private JdbcType jdbcType;
// resultMap子节点的typeHandler属性,类型转换器
private TypeHandler<?> typeHandler;
// join方式关联查询时会用到
private String nestedResultMapId;
// 子查询会用到
private String nestedQueryId;
// 对应节点的notNullColumns属性拆分后的结果
private Set<String> notNullColumns;
// resultMap子节点的columnPrefix属性
private String columnPrefix;
private List<ResultFlag> flags;
private List<ResultMapping> composites;
private String resultSet;
private String foreignColumn;
// 是否延迟加载
private boolean lazy;

这两个类的结构如下所示:
mybatismapperresultMaping.png
解析resultMap标签:

private void resultMapElements(List<XNode> list) {
    for (XNode resultMapNode : list) {
      try {
        // 调用resultMapElement方法解析mapper中的resultMap标签
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
}

private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取resultMap的type属性,如果type属性不存在,则取ofType属性,否则取resultType,如果不存在继续取javaType.
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    // 获取type的Class类型
    Class<?> typeClass = resolveClass(type);
    if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    // 获取resultMap的子节点
    for (XNode resultChild : resultChildren) {
      // 如果子节点的为constructor
      if ("constructor".equals(resultChild.getName())) {
        // 解析constructor节点
        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);
        }
        // 解析包括result在内的其他名称的标签
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 解析resultMap的id属性
    String id = resultMapNode.getStringAttribute("id",
            resultMapNode.getValueBasedIdentifier());
    // 解析resultMap的extends属性
    String extend = resultMapNode.getStringAttribute("extends");
    // 解析resultMap的autoMapping属性
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
}

buildResultMappingFromContext方法如下所示:

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      // 解析比如result标签中的property的属性
      property = context.getStringAttribute("property");
    }
    // 解析标签中的column属性
    String column = context.getStringAttribute("column");
    // 解析标签中的javaType属性
    String javaType = context.getStringAttribute("javaType");
    // 解析标签中jdbcType属性
    String jdbcType = context.getStringAttribute("jdbcType");
    // 解析标签中的select属性
    String nestedSelect = context.getStringAttribute("select");
    // 解析标签中嵌套的resultMap属性
    String nestedResultMap = context.getStringAttribute("resultMap", () ->
        processNestedResultMappings(context, Collections.emptyList(), resultType));
    // 解析notNullColumn属性
    String notNullColumn = context.getStringAttribute("notNullColumn");
    // 解析columnPrefix属性
    String columnPrefix = context.getStringAttribute("columnPrefix");
    // 解析typeHandler属性
    String typeHandler = context.getStringAttribute("typeHandler");
    // 解析resultSet属性
    String resultSet = context.getStringAttribute("resultSet");
    // 解析foreignColumn属性
    String foreignColumn = context.getStringAttribute("foreignColumn");
    // 判断是否延迟加载
    boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
    Class<?> javaTypeClass = resolveClass(javaType);
    Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // 调用builderAssistant的buildResultMapping方法构建ResultMapping对象
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
}

resultMapResolver.resolve方法最终会调用addResultMap方法:

public ResultMap addResultMap(
  String id,
  Class<?> type,
  String extend,
  Discriminator discriminator,
  List<ResultMapping> resultMappings,
  Boolean autoMapping) {
    // resultMap的完整ID是namespace.id
    id = applyCurrentNamespace(id, false);
    // 父resultMap的ID
    extend = applyCurrentNamespace(extend, true);
    
    if (extend != null) {
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      ResultMap resultMap = configuration.getResultMap(extend);
      List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      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));
      }
      resultMappings.addAll(extendedResultMappings);
    }
    // 构建ResultMap
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
    // 注册resultMap到Configuration中
    configuration.addResultMap(resultMap);
    return resultMap;
}

最终注册到Configuration中的ResultMap的数据结构如下:
mybatismapperresultMapData.png

3.4、解析sql标签

解析sql标签的代码如下所示:

private void sqlElement(List<XNode> list) {
    // 调用sqlElement的重载方法
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
}

private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    // 循环解析所有的SQL标签
    for (XNode context : list) {
      // 获取SQL标签中databaseId属性
      String databaseId = context.getStringAttribute("databaseId");
      // 获取SQL标签的ID属性
      String id = context.getStringAttribute("id");
      // 转换成namespace.id的格式
      id = builderAssistant.applyCurrentNamespace(id, false);
      // 检测SQL标签中的databaseId是否与configuration中的ID是否一致
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        // 记录到Configuration中的sqlFragements集合中
        sqlFragments.put(id, context);
      }
    }
}

3.5、解析select|insert|update|delete标签

MappedStatement类的字段如下:

private String resource;
// 持有configuration对象
private Configuration configuration;
// 标签的ID
private String id;
private Integer fetchSize;
private Integer timeout;
// 标签的类型
private StatementType statementType;
private ResultSetType resultSetType;
// 标签中的SQL
private SqlSource sqlSource;
// 二级缓存
private Cache cache;
private ParameterMap parameterMap;
// resultMap
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
// SQL语句解析驱动
private LanguageDriver lang;
private String[] resultSets;

解析方法如下所示:

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) {
      // 使用XMLStatementBuilder解析statement类型的标签
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
}

XMLStatementBuilder的parse方法如下:

public void parseStatementNode() {
    // 获取id属性
    String id = context.getStringAttribute("id");
    // 获取databaseId属性
    String databaseId = context.getStringAttribute("databaseId");
    // 判断是否是当前databaseId
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }
    
    // 获取节点名select|update|insert|delete
    String nodeName = context.getNode().getNodeName();
    // 转换成SqlCommand的类型
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 是否是select节点
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 获取flushCache属性
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 获取useCache属性,如果不存在,默认情况下是判断是否是select标签
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    // 获取resultOrdered属性
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
    
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());
    
    // 获取parameterType属性
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 获取lang属性
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    // 解析selectKey属性
    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    KeyGenerator keyGenerator;
    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.PREPARED
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    // 获取fetchSize属性
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // 获取timeout属性
    Integer timeout = context.getIntAttribute("timeout");
    // 获取parameterMap属性
    String parameterMap = context.getStringAttribute("parameterMap");
    // 获取resultType属性
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    // 获取resultMap属性
    String resultMap = context.getStringAttribute("resultMap");
    // 获取resultSetType属性
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    // 获取keyProperty属性
    String keyProperty = context.getStringAttribute("keyProperty");
    // 获取keyColumn属性
    String keyColumn = context.getStringAttribute("keyColumn");
    // 获取resultSets属性
    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);
}

至此,mapper.xml文件的内容已经全部解析完成。

4、解析annotation类型mapper

解析annotation类型的mapper是通过MapperAnnotationBuilder类来完成的。
该类中的字段如下:

// 存储Mybatis中支持的SQL语句类型的注解
private static final Set<Class<? extends Annotation>> SQL_ANNOTATION_TYPES = new HashSet<>();
// 存储SQL提供类的注解
private static final Set<Class<? extends Annotation>> SQL_PROVIDER_ANNOTATION_TYPES = new HashSet<>();

// 持有Configuration引用
private final Configuration configuration;
// 持有Mapper中的元素构造器,实际的构造操作
private final MapperBuilderAssistant assistant;
// mapper类型
private final Class<?> type;

// 备注:*Provider注解是用来标注返回SQL语句的类的方法
static {
    // 定义了Mybatis中的SQL语句类型
    SQL_ANNOTATION_TYPES.add(Select.class);
    SQL_ANNOTATION_TYPES.add(Insert.class);
    SQL_ANNOTATION_TYPES.add(Update.class);
    SQL_ANNOTATION_TYPES.add(Delete.class);
    
    // 定义了Mybatis中SQL语句提供注解类型
    SQL_PROVIDER_ANNOTATION_TYPES.add(SelectProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(InsertProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(UpdateProvider.class);
    SQL_PROVIDER_ANNOTATION_TYPES.add(DeleteProvider.class);
}

parse方法如下:

public void parse() {
    // 获取mapper类型的名称
    String resource = type.toString();
    // 判断这个mapper是否被解析过
    if (!configuration.isResourceLoaded(resource)) {
      // 尝试解析下这个mapper的xml文件,比如com.matrix.dao.UserMapper.xml
      loadXmlResource();
      // 标记解析完成
      configuration.addLoadedResource(resource);
      // 设置当前的命名空间com.matrix.dao.UserMapper
      assistant.setCurrentNamespace(type.getName());
      // 解析Cache
      parseCache();
      // 解析CacheRef
      parseCacheRef();
      // 遍历全部的方法
      for (Method method : type.getMethods()) {
         // 如果不是Statement方法,则跳过
        if (!canHaveStatement(method)) {
          continue;
        }
        // 如果Sql类型是select并且不存在ResultMap注解,该注解是用来引用mapper中的其他@Results
        if (getSqlCommandType(method) == SqlCommandType.SELECT && method.getAnnotation(ResultMap.class) == null) {
          // 解析ResultMap
          parseResultMap(method);
        }
        try {
          // 解析Statement
          parseStatement(method);
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
}

4.1、解析Cache

@CacheNamespace注解主要用于mybatis二级缓存,等同于属性。默认情况下,MyBatis 3 没有开启二级缓存,要开启二级缓存,需要在SQL 映射文件(mapper.xml)中添加一行:,当然,前提还需要在全局配置文件中开启缓存:

<setting name="cacheEnabled" value="true"/>

parserCache方法如下:

private void parseCache() {
    // 解析Mapper接口类型的CacheNamespace注解
    CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class);
    if (cacheDomain != null) {
      // 获取CacheNamespace注解的size属性
      Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size();
      // 获取CacheNamespace注解的flushInterval属性
      Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval();
      // 获取CacheNamespace注解的properties
      Properties props = convertToProperties(cacheDomain.properties());
      // 调用MapperBuilderAssistant创建Cache
      assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props);
    }
}

4.2、解析CacheRef

解析Mapper接口中的CacheNamespaceRef注解

private void parseCacheRef() {
    // 获取Mapper接口中的CacheNamespaceRef注解
    CacheNamespaceRef cacheDomainRef = type.getAnnotation(CacheNamespaceRef.class);
    if (cacheDomainRef != null) {
      // 获取CacheNamespaceRef注解中的value属性
      Class<?> refType = cacheDomainRef.value();
      // 获取CacheNamespaceRef注解中的name属性
      String refName = cacheDomainRef.name();
      // 如果value属性为void且name属性为空,则抛出BuilderException异常
      if (refType == void.class && refName.isEmpty()) {
        throw new BuilderException("Should be specified either value() or name() attribute in the @CacheNamespaceRef");
      }
      // 如果refType和refName属性都不存在,也抛出BuilderException异常
      if (refType != void.class && !refName.isEmpty()) {
        throw new BuilderException("Cannot use both value() and name() attribute in the @CacheNamespaceRef");
      }
      // 获取名称空间属性
      String namespace = (refType != void.class) ? refType.getName() : refName;
      try {
        // 调用MapperBuilderAssistant指定名称空间
        assistant.useCacheRef(namespace);
      } catch (IncompleteElementException e) {
        configuration.addIncompleteCacheRef(new CacheRefResolver(assistant, namespace));
      }
    }
}

4.3、解析statement

void parseStatement(Method method) {
    Class<?> parameterTypeClass = getParameterType(method);
    LanguageDriver languageDriver = getLanguageDriver(method);
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    if (sqlSource != null) {
      Options options = method.getAnnotation(Options.class);
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;
    
      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = null;
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
        keyGenerator = NoKeyGenerator.INSTANCE;
      }
    
      if (options != null) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }
    
      String resultMapId = null;
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      if (resultMapAnnotation != null) {
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        for (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
      } else if (isSelect) {
        // 解析resultMap
        resultMapId = parseResultMap(method);
      }
      // 构建MappedStatement,并注册到Configuration中。
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
}

创建MappedStatement对象并注册到Configuration中。

至此整个mapper的解析就完成了。