MyBatis源码简读——2.1.2 mapper映射文件解析

映射文件的解析入口是 org.apache.ibatis.builder.xml.XMLMapperBuilder

其主要方法和属性

MyBatis源码简读——2.1.2 mapper映射文件解析

属性

首先简单介绍下几个属性的作用

  • 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解析到底解析什么,解析成了什么。

MyBatis源码简读——2.1.2 mapper映射文件解析