《Mybatis源码》第4章 解析XML映射文件

XMLMapperBuilder

上文我们介绍了通过XMLConfigBuilder来加载全局配置文件mybatis-config.xml,读取了各种配置,但里面比较重要的读取mapper,因为我们使用Mybatis框架,更多的关注点实在mapper映射文件的sql编写上,接下来我们详细看一下

1.调用链

上文说到在XMLConfigBuilder类中的parse()里面调用mapperElement()读取mapper文件,接下来看一下如何加载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");
          // 第一种情况  通过resource获取mapper.xml
          if (resource != null && url == null && mapperClass == null) {
            // 错误日志,见单独文章
            ErrorContext.instance().resource(resource);
            // 读取文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 创建XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 转换
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            // 第二种情况 通过url获取mapper.xml
            ErrorContext.instance().resource(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) {
            // 第三种情况 通过类路径获取mapper.class
            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.");
          }
        }
      }
    }
  }

上面代码中我们看到可以通过3种方式加载mapper文件,第一种和第二种都是加载xml文件,而第三种是加载我们的dao接口,先介绍如何加载xml文件

加载XML文件,首先读取文件,然后创建了XMLMapperBuilder,然后调用了parse(),我们发现这和之前读取全局配置文件用的XMLConfigBuilder逻辑差不多,其实这两个类都是BaseBuilder的子类,首先看一下构造方法,就是进行了初始赋值

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    // 构建映射器处理
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    // XML解析器
    this.parser = parser;
    // SQL碎片
    this.sqlFragments = sqlFragments;
    // 资源路径
    this.resource = resource;
  }

解析mapper文件的parse()方法

 public void parse() {
    // 判断该文件是否重复加载
    if (!configuration.isResourceLoaded(resource)) {
      // 解析mapper标签
      configurationElement(parser.evalNode("/mapper"));
      // 添加到已加载的集合里面
      configuration.addLoadedResource(resource);
      // 找出对应当前的mapper的java类型,并添加到全局配置中
      bindMapperForNamespace();
    }
    // 重新解析Mybatis全局配置信息中未能完成解析的ResultMap标签信息
    parsePendingResultMaps();
    // 重新解析Mybatis全局配置信息中未能完成解析的CacheRef标签信息
    parsePendingCacheRefs();
    // 重新解析Mybatis全局配置信息中未能完成解析的DML标签信息
    parsePendingStatements();
  }

我们看到在parse()方法里面调用了很多方法,其实这些方法大都在同一个类中,下面会依次解析,先看如何解析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");
      }
      // 设置当前namespace
      builderAssistant.setCurrentNamespace(namespace);
      // 解析cache-ref 其它命名空间的缓存配置
      cacheRefElement(context.evalNode("cache-ref"));
      // 解析 该命名空间的缓存配置
      cacheElement(context.evalNode("cache"));
      // 解析parameterMap标签 但是该标签已经被官网标注废弃
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析resultMap
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 解析sql
      sqlElement(context.evalNodes("/mapper/sql"));
      // 构建每个DML标签对应的XMLStatmentBuilder,并通过XMLStatementBuilder对DML标签进行解析
      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映射的允许使用的标签全部解析,同样从这里我们可以看到mapper文件允许的标签,这些可以在org.apache.ibatis.builder.xmlmybatis-3-mapper.dtd里面看到,下面会介绍一些常用标签的使用

<!ELEMENT mapper (cache-ref | cache | resultMap* | parameterMap* | sql* | insert* | update* | delete* | select* )+>

2.解析cache标签

 private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      // type:指定自定义缓存的全类名(实现Cache接口即可),PERPETUAL是别名,对应PerpetualCache缓存类
      // 存储二级缓存的底层结构 可以自定义
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      // 缓存回收策略 默认LRU
      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);
      // 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      // 创建cache实例
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

看一下useNewCache()方法,该方法通过CacheBuilder()创建缓存,然后存到全局配置中

// 该方法在 builderAssistant 助理类中
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.解析resultMap标签

调用解析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
      }
    }
  }
 
  // 解析
  private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    // 第2个参数为空集合
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  }
  
 /**
   * 解析resultMap标签,构建resultMap实例
   * @param resultMapNode resultMap节点
   * @param additionalResultMappings 额外的resultMapping
   * @return 构建成功的ResultMap实例
   */
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    // 存储操作信息  当发现异常的时候 便于显示
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // 获取id 没有设置mybatis就会自动配置一个
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    //ResultMap对应的javaType的包名+类名,从这里可以看出,有四个属性都可以指定javaType,优先级为:type=>ofType=>resultType=>javaType
    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) {
      // 子标签为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<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          // 这里存储一个枚举,因为解析上面的constructor和这里的id底层解析是用的同一个方法
          // 所以需要通过参数来区分 所以这里编辑了一下
          flags.add(ResultFlag.ID);
        }
        // 通过buildResultMappingFromContext创建ResultMapping
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    // 创建resultMap解析器
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      // 调用解析方法
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      // 异常时,将XML Statment构建器添加到Mybatis全局配置信息中,后期再尝试解析
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

在上面的方法中为了解析constructor,discriminator和其它标签,又分别调用了另外的方法,现在放在一起,仔细看一下,就是挨个通过遍历,挨个获取属性,然后创建 RequestMapping

 // 解析constructor标签
 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>();
      // 注意这里: 如果是arg标签 则flags里面只有1个枚举
      flags.add(ResultFlag.CONSTRUCTOR);
      // 如果是idArg标签 则flags里面有2个枚举
      if ("idArg".equals(argChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      // 构造方法标签直接添加到resultMappings集合中
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
  }
  // 解析discriminator鉴别器
  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);
  }
 // 解析其余标签
 private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    String property;
    // 通过传递的参数来区分是解析constructor标签还是其它
    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);
  }

在上面代码块中的第3个方法buildResultMappingFromContext(),这个方法主要解析除了constructordiscriminator外的其它标签,包括:idresultassociationcollection标签,那么例如idresult这个很简单,直接封装到RequestMapping对象里面即可,那么这个1对1,和1对多,如何解析,这个需要看一下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) {
        // 主要看这里 会继续调用resultMapElement()方法
        // 上面解析resultMap标签的时候 用这个方法 这里解析association、collection、case 相当于嵌套解析 类似于递归
        // 经过这个处理以后,已经生成一个resultMap,并且把它添加到了configuration里面
        ResultMap resultMap = resultMapElement(context, resultMappings);
        // 返回结果 下面是几个结果示例
        // com.jianan.springtest.dao.CarMapper.mapper_resultMap[list]_association[person]
        // com.jianan.springtest.dao.CarMapper.mapper_resultMap[list]_collection[persons]
        return resultMap.getId();
      }
    }
    return null;
  }

ResultMapping

上面的代码思路,主要是从resultMap标签获取属性,然后遍历子标签,分别调用方法,把内容封装到ResultMapping中,我们继续回到resultMapElement(),方法最后又创建了ResultMapResolver解析器,进行解析,然后我们先后介绍一下RequestMappingResultMapResolver 两个类的结构

// 这里简单看一下它的字段
public class ResultMapping {

  private Configuration configuration;
  // java字段名
  private String property;
  // jdbc列名
  private String column;
  private Class<?> javaType;
  private JdbcType jdbcType;
  // 类型处理器
  private TypeHandler<?> typeHandler;
  // 嵌套的resultMap 这里对应着其它resultMap的id
  private String nestedResultMapId;
  // 嵌套的select查询
  private String nestedQueryId;
  private Set<String> notNullColumns;
  private String columnPrefix;
  // 处理后的标志,标志共两个:id和constructor
  private List<ResultFlag> flags;
  private List<ResultMapping> composites;
  private String resultSet;
  private String foreignColumn;
  // 延迟加载
  private boolean lazy;

}

ResultMapResolver

public class ResultMapResolver {
  // mapper创建助理
  private final MapperBuilderAssistant assistant;
  // 主键
  private final String id;
  // 结果类型
  private final Class<?> type;
  private final String extend;
  // discriminator标签
  private final Discriminator discriminator;
  // resultMap中的子标签集合 里面包含子标签和构造方法标签
  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;
  }
  
  // 解析获取ResultMap对象
  public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
  }
}

上面通过助理类创建ResultMap,接下来继续看下resolve()中调用的addResultMap()

// 这个方法看着很长 其实咱们用的部分很少
public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);
    // 注意看这里 会判断extend 这个属性我们很少用 所以这里跳过
    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<ResultMapping>(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) {
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      resultMappings.addAll(extendedResultMappings);
    }
    // 这里创建的是ResultMap的内部类Builder,然后通过build()方法,创建ResultMap
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        // 通过这个build()方法来构建
        .build();
    // 添加到配置文件中
    configuration.addResultMap(resultMap);
    return resultMap;
  }

ResultMap

上面代码经过判断,首先会创建ResultMap,然后添加到configuration里面,分别看一下,首先我们看一下ResultMap

// 1个ResultMap对应1个resultMap标签,里面的RequestMapping对应子标签
public class ResultMap {
  private Configuration configuration;
  
  // <resultMap>的id属性
  private String id;
  // <resultMap>的type属性
  private Class<?> type;
  // 除<discriminator>元素外的所有属性映射关系
  private List<ResultMapping> resultMappings;
  // 所有属性映射中带有ID标志的映射关系,包括<id>元素和<constructor>的<idArg>子元素
  private List<ResultMapping> idResultMappings;
  // 带有Constructor标志的映射关系
  private List<ResultMapping> constructorResultMappings;
  // 所有属性映射中不带有Constructor标志的映射关系
  private List<ResultMapping> propertyResultMappings;
  // 所有属性映射中的column属性的集合
  private Set<String> mappedColumns;
  // 所有属性映射中的properties属性的集合
  private Set<String> mappedProperties;
  // 鉴别器
  private Discriminator discriminator;
  // 是否包含嵌套的结果映射
  private boolean hasNestedResultMaps;
  // 是否包含嵌套的结果查询
  private boolean hasNestedQueries;
  // 自动映射
  private Boolean autoMapping;
  
  // 私有构造方法
  private ResultMap() {
  }
  
  // 静态内部类
  public static class Builder {
    // 日志
    private static final Log log = LogFactory.getLog(Builder.class);
    // 创建对象
    private ResultMap resultMap = new ResultMap();
    // 重载构造方法
    public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
      this(configuration, id, type, resultMappings, null);
    }
    // 创建Builder的时候,代表创建了一个ResultMap
    public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings, Boolean autoMapping) {
      resultMap.configuration = configuration;
      resultMap.id = id;
      resultMap.type = type;
      resultMap.resultMappings = resultMappings;
      resultMap.autoMapping = autoMapping;
    }
   
    // 鉴别器赋值
    public Builder discriminator(Discriminator discriminator) {
      resultMap.discriminator = discriminator;
      return this;
    }
    // 获取类型
    public Class<?> type() {
      return resultMap.type;
    }
    // 构建方法
    public ResultMap build() {
      if (resultMap.id == null) {
        throw new IllegalArgumentException("ResultMaps must have an id");
      }
      // 初始化各种对象
      resultMap.mappedColumns = new HashSet<String>();
      resultMap.mappedProperties = new HashSet<String>();
      resultMap.idResultMappings = new ArrayList<ResultMapping>();
      resultMap.constructorResultMappings = new ArrayList<ResultMapping>();
      resultMap.propertyResultMappings = new ArrayList<ResultMapping>();
      final List<String> constructorArgNames = new ArrayList<String>();
      // 遍历所有的子标签
      for (ResultMapping resultMapping : resultMap.resultMappings) {
        // 嵌套select子查询
        // 这里用 || 就是因为我们需要遍历处理子标签 只要有1个子标签是嵌套查新,这个map就包含嵌套查询 
        resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
        // 嵌套映射
        resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || (resultMapping.getNestedResultMapId() != null && resultMapping.getResultSet() == null);
        // 列名 添加到集合
        final String column = resultMapping.getColumn();
        if (column != null) {
          resultMap.mappedColumns.add(column.toUpperCase(Locale.ENGLISH));
        } else if (resultMapping.isCompositeResult()) {
          for (ResultMapping compositeResultMapping : resultMapping.getComposites()) {
            final String compositeColumn = compositeResultMapping.getColumn();
            if (compositeColumn != null) {
              resultMap.mappedColumns.add(compositeColumn.toUpperCase(Locale.ENGLISH));
            }
          }
        }
        final String property = resultMapping.getProperty();
        // 字段名 添加到集合
        if(property != null) {
          resultMap.mappedProperties.add(property);
        }
        // 判断子标签是否为构造方法
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          // 添加到构造方法集合
          resultMap.constructorResultMappings.add(resultMapping);
          // 设置构造方法参数集合
          if (resultMapping.getProperty() != null) {
            constructorArgNames.add(resultMapping.getProperty());
          }
        } else {
          // 否则添加到propertyResultMappings
          resultMap.propertyResultMappings.add(resultMapping);
        }
        // 判断子标签是否包含id
        if (resultMapping.getFlags().contains(ResultFlag.ID)) {
          resultMap.idResultMappings.add(resultMapping);
        }
      }
      // 如果没有id标签 那么全部都添加到idResultMappings
      if (resultMap.idResultMappings.isEmpty()) {
        resultMap.idResultMappings.addAll(resultMap.resultMappings);
      }
      if (!constructorArgNames.isEmpty()) {
        // 获取构造方法的参数名
        final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
        if (actualArgNames == null) {
          throw new BuilderException("Error in result map '" + resultMap.id
              + "'. Failed to find a constructor in '"
              + resultMap.getType().getName() + "' by arg names " + constructorArgNames
              + ". There might be more info in debug log.");
        }
        // 排序 让constructorResultMappings和构造方法的形参顺序相同
        Collections.sort(resultMap.constructorResultMappings, new Comparator<ResultMapping>() {
          @Override
          public int compare(ResultMapping o1, ResultMapping o2) {
            int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
            int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
            return paramIdx1 - paramIdx2;
          }
        });
      }
      // 设置只读
      // lock down collections
      resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
      resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
      resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
      resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
      resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
      return resultMap;
    }
    
    // 通过constructor标签设置的参数来匹配构造方法
    private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
      // 返回类型的构造方法
      Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
      for (Constructor<?> constructor : constructors) {
        // 参数类型
        Class<?>[] paramTypes = constructor.getParameterTypes();
        if (constructorArgNames.size() == paramTypes.length) {
          // 参数名
          List<String> paramNames = getArgNames(constructor);
          if (constructorArgNames.containsAll(paramNames)
              && argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
            return paramNames;
          }
        }
      }
      return null;
    }

    private boolean argTypesMatch(final List<String> constructorArgNames,
        Class<?>[] paramTypes, List<String> paramNames) {
      for (int i = 0; i < constructorArgNames.size(); i++) {
        Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
        Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
        if (!actualType.equals(specifiedType)) {
          if (log.isDebugEnabled()) {
            log.debug("While building result map '" + resultMap.id
                + "', found a constructor with arg names " + constructorArgNames
                + ", but the type of '" + constructorArgNames.get(i)
                + "' did not match. Specified: [" + specifiedType.getName() + "] Declared: ["
                + actualType.getName() + "]");
          }
          return false;
        }
      }
      return true;
    }
   
    // 获取构造方法的形参名
    private List<String> getArgNames(Constructor<?> constructor) {
      if (resultMap.configuration.isUseActualParamName() && Jdk.parameterExists) {
        return ParamNameUtil.getParamNames(constructor);
      } else {
        List<String> paramNames = new ArrayList<String>();
        final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
        int paramCount = paramAnnotations.length;
        for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
          String name = null;
          for (Annotation annotation : paramAnnotations[paramIndex]) {
            if (annotation instanceof Param) {
              name = ((Param) annotation).value();
              break;
            }
          }
          paramNames.add(name != null ? name : "arg" + paramIndex);
        }
        return paramNames;
      }
    }
  }

  // 省略一些setter/getter....
}

上面看完了创建的代码,接下来会把resultMap添加到configuration中,接下来看一下,我这里截取一下重要的

public class Configuration {
    // 创建的同样为内部类StrictMap
    // key为id  value为值
    protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");

    // 添加到配置
    public void addResultMap(ResultMap rm) {
        resultMaps.put(rm.getId(), rm);
        checkLocallyForDiscriminatedNestedResultMaps(rm);
        checkGloballyForDiscriminatedNestedResultMaps(rm);
    }
}

总结:解析resultMap标签,1个标签对应1个RequestMap对象,1个RequestMapping对象,对应1个子标签,如果是1对1或1对多,那么也会创建RequestMap,这些RequestMap会以同等级存储到configuration里面,也就是configuration里面的resultMaps,存储不光包括resultMap标签,连嵌套查询和嵌套映射也会创建成resultMaps存进去,但这些ResultMap之间是存在相互引用关系的,那么怎么判断,就是通过上面介绍的ResultMap中的字段 hasNestedResultMaps 是否包含嵌套映射,hasNestedQueries 是否包含嵌套查询 以及 ResultMapping中的字段 nestedResultMapId 嵌套映射对应的id nestedQueryId 嵌套查询对应的id。

这次整个resultMap解析就完成了 还是非常麻烦的 ,需要自己Debug挨个查看 ,下面提供Debug结果示例:
请添加图片描述

4.解析sql标签

解析sql标签的方法sqlElement()

  // 解析结果
  private final Map<String, XNode> sqlFragments;

  // 遍历调用子方法解析
  private void sqlElement(List<XNode> list) throws Exception {
    // 第一次用于处理 databaseId 与全局Configuration实例的 databaseId 一致的节点,如果全局没有配置 则直接跳过
    // 也就是查找和全局配置的databaseId相匹配的sql
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    // 第二次处理节点的 databaseId 为空的情况
    // 也就是查找没有配置databaseId的情况
    sqlElement(list, null);
  }
  
  // 匹配当前配置的数据库Id
  private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
      // 注意这里从sql标签获取databaseId属性肯定为null
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      // 检查ID是否简写,简写就应用当前命名空间
      // 需要的格式为 包名+id名 例如:com.jianan.mapper.UserMapper.test
      // 这里这样做的目的是两个mapper映射文件的sql语句的id允许相同,所以直接通过全部名称来区分
      id = builderAssistant.applyCurrentNamespace(id, false);
      // 注意看这里和上面会不一样了   因为我们的sql标签没有databaseId属性
      // 所以这个判断就主要根据requiredDatabaseId, 如果存在则肯定false  不存在则返回true
      // 然后结合上面方法调用了两次 
      // 当一次调用的时候 requiredDatabaseId存在 则调用 那么下面肯定返回false,也不会put()
      // 当二次调用的时候,方法会直接返回true 会直接添加
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        // 这里的存储方法在Configuration类里面被重写了
        sqlFragments.put(id, context);
      }
    }
  }

在上面的代码中会通过databaseIdMatchesCurrent()来判断是否把当前的sql添加到结果集

这个代码整合上面的思路就是:

  1. 如果全局配置了databaseId,那么会找到和其匹配的sql 和 没有设置databaseId的sql,也就是找到匹配的sql和通用的sql,如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃 (但是我们的sql标签并没有databaseId属性,那么这么判断有何用?我猜想可能为了以后扩展吧)
  2. 如果全局没有配置databaseId,那么就会直接找到没有设置databaseId的sql,也就是直接找到通用的sql
 private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    // 全局配置了databaseId
    if (requiredDatabaseId != null) {
      // 找到和全局databaseId相等的sql
      if (!requiredDatabaseId.equals(databaseId)) {
        return false;
      }
    } else {// 全局没有配置databaseId
      if (databaseId != null) {
        // 全局没有配置  而这个sql配置了 直接跳过
        return false;
      }
      // 这个sql已经被添加了
      // skip this fragment if there is a previous one with a not null databaseId
      if (this.sqlFragments.containsKey(id)) {
        XNode context = this.sqlFragments.get(id);
        // 这里的意思是两个sql的id相同,一个带databaseId,另一个不带,则会忽略不带的
        if (context.getStringAttribute("databaseId") != null) {
          return false;
        }
      }
    }
    return true;
  }

我们的sql标签是不允许id重复的,那么在哪里进行判断的?它是通过databaseIdMatchesCurrent()下面一行代码的put()方法进行的,那么我们记得我们的map接口存储时,如果key相同则会覆盖,这里怎么实现的?

我们首先看一下sqlFragments是如何来的,我们回到之前XMLConfigBuilder类的mapperElement()方法

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对象的时候,会从configuration获取到sqlFragments 然后传入到构造方法
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(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.");
          }
        }
      }
    }
  }

接下来就去看一下Configuration类,我截取部分重要的内容

public class Configuration {
	// 发现在加载 Configuration 类的时候就直接初始化了sqlFragments
	// 这里创建的是StrictMap
	protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");

	// StrictMap为继承Map的内部类
	protected static class StrictMap<V> extends HashMap<String, V> {
        // 构造方法
    	public StrictMap(String name) {
      		super();
      		this.name = name;
    	}
        
        public V put(String key, V value) {
          // 如果该id已经存在,则抛出异常
          if (containsKey(key)) {
            throw new IllegalArgumentException(name + " already contains value for " + key);
          }
          // 报名  例如:com.jianan.mapper.UserMapper.test
          if (key.contains(".")) {
            // 获取类名  例如: test
            final String shortKey = getShortName(key);
            if (super.get(shortKey) == null) {
              // 第一次添加
              super.put(shortKey, value);
            } else {
              super.put(shortKey, (V) new Ambiguity(shortKey));
            }
          }
          // 第二次添加
          return super.put(key, value);
        }
	}
}

从上面的put()中我们看到,同一个sql标签会往集合里面添加2次,2次的value都是这个标签,第1次的key为全限定名,例如: com.jianan.mapper.UserMapper.test,第2次的key为简写名,例如:test

5.解析DML标签

解析基础的标签buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

  private void buildStatementFromContext(List<XNode> list) {
    // 这个大体思路和上面一样
    // 第一次用于处理 databaseId 与全局Configuration实例的 databaseId 一致的节点
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    // 第二次匹配通用的sql
    buildStatementFromContext(list, null);
  }
  
  // 创建sql
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      // 创建XMLStatementBuilder对象
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 转换sql节点
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        // 如果发生异常,则添加到不明确的sql集合里面
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

XMLStatementBuilder

上面的代码就是遍历节点,然后依次借助XMLStatementBuilder类通过parseStatementNode()节点转换

/**
  * 解析DML标签
  * <p>将DML标签中的include标签替换成对应sql标签下的所有子标签</p>
  * <p>将DML标签中的selectKey标签封装成KeyGenerator对象,然后添加到Mybatis全局配置信息信息,然后删除DML标签里的所有selectKey标签</p>
  * <p>将DML标签,封装成MapperStatement对象,然后添加到Mybatis全局配置信息中</p>
  */
public void parseStatementNode() {
    // 下面就是依次从标签获取属性
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");
    // 判断该sql的databaseId是否匹配  和  该sql是否为重复添加
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    Integer fetchSize = context.getIntAttribute("fetchSize");
    Integer timeout = context.getIntAttribute("timeout");
    String parameterMap = context.getStringAttribute("parameterMap");
    String parameterType = context.getStringAttribute("parameterType");
    // 这里需要通过TypeAliasRegistry类的resolveClass来解析  因为可能是别名
    Class<?> parameterTypeClass = resolveClass(parameterType);
    String resultMap = context.getStringAttribute("resultMap");
    String resultType = context.getStringAttribute("resultType");
    // lang:指定该DML标签使用的语言驱动
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);
    // 结果类型 解析
    Class<?> resultTypeClass = resolveClass(resultType);
    String resultSetType = context.getStringAttribute("resultSetType");
    // 默认PreparedStatement
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
    // 通过sql标签来区分是 增删改查
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 是否刷新缓存 非select元素设置为true 都会刷新缓存
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 是否缓存结果 对 select 元素为 true 代表默认缓存
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    // 将include标签转换成对应的sql片段  并将其中的${xxx}占位符替换为真实的参数
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // 转换selectKey 之后删除
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // 英文已经标注了 转换sql
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    // 用于获取生成主键
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    // 拼装selectKey标签的Id,selectKey标签的Id=DML标签ID+!selectKey
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    // 检查id名称
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    // 如果Mybatis全局配置信息中有keyStatementId对应的KeyGenerator
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      // 判断
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }
    // 构建MapperStatement对象,并添加到全局配置信息中
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

上面用到了databaseIdMatchesCurrent()来判断sql的databaseId,该

 private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
    // 全局配置了databaseId  则找到匹配的sql
    if (requiredDatabaseId != null) {
      if (!requiredDatabaseId.equals(databaseId)) {
        return false;
      }
    } else {
      // 只在sql里面配置的  不管
      if (databaseId != null) {
        return false;
      }
      // skip this statement if there is a previous one with a not null databaseId
      // 检查ID是否简写,简写就应用当前命名空间
      // 需要的格式为 包名+id名 例如:com.jianan.mapper.UserMapper.test
      // 这里这样做的目的是两个mapper映射文件的sql语句的id允许相同,所以直接通过全部名称来区分
      id = builderAssistant.applyCurrentNamespace(id, false);
      // 判断是否已经包含
      if (this.configuration.hasStatement(id, false)) {
        MappedStatement previous = this.configuration.getMappedStatement(id, false); // issue #2
        if (previous.getDatabaseId() != null) {
          return false;
        }
      }
    }
    return true;
  }

parseStatementNode()中通过includeParser.applyIncludes(context.getNode()); 来转换include标签,将其替换成sql片段,并将其中的${xxx}占位符替换为真实的参数

public void applyIncludes(Node source) {
    Properties variablesContext = new Properties();
    Properties configurationVariables = configuration.getVariables();
    if (configurationVariables != null) {
      variablesContext.putAll(configurationVariables);
    }
    applyIncludes(source, variablesContext, false);
  }

  /**
   * Recursively apply includes through all SQL fragments.
   * @param source Include node in DOM tree
   * @param variablesContext Current context for static variables with values
   */
  private void applyIncludes(Node source, final Properties variablesContext, boolean included) {
    if (source.getNodeName().equals("include")) {
      Node toInclude = findSqlFragment(getStringAttribute(source, "refid"), variablesContext);
      Properties toIncludeContext = getVariablesContext(source, variablesContext);
      applyIncludes(toInclude, toIncludeContext, true);
      if (toInclude.getOwnerDocument() != source.getOwnerDocument()) {
        toInclude = source.getOwnerDocument().importNode(toInclude, true);
      }
      source.getParentNode().replaceChild(toInclude, source);
      while (toInclude.hasChildNodes()) {
        toInclude.getParentNode().insertBefore(toInclude.getFirstChild(), toInclude);
      }
      toInclude.getParentNode().removeChild(toInclude);
    } else if (source.getNodeType() == Node.ELEMENT_NODE) {
      if (included && !variablesContext.isEmpty()) {
        // replace variables in attribute values
        NamedNodeMap attributes = source.getAttributes();
        for (int i = 0; i < attributes.getLength(); i++) {
          Node attr = attributes.item(i);
          attr.setNodeValue(PropertyParser.parse(attr.getNodeValue(), variablesContext));
        }
      }
      NodeList children = source.getChildNodes();
      for (int i = 0; i < children.getLength(); i++) {
        applyIncludes(children.item(i), variablesContext, included);
      }
    } else if (included && source.getNodeType() == Node.TEXT_NODE
        && !variablesContext.isEmpty()) {
      // replace variables in text node
      source.setNodeValue(PropertyParser.parse(source.getNodeValue(), variablesContext));
    }
  }

parseStatementNode()我们在方法的最后又是通过builderAssistant助理类创建MapperStatement

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) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }
    // 类名转换
    id = applyCurrentNamespace(id, false);
    // sql语句类型
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
    // 创建对象 该对象为MappedStatement的静态内部类Build
    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)
        // 在这里会获取到resultMap 下面介绍了
        .resultMaps(getStatementResultMaps(resultMap, resultType, id))
        .resultSetType(resultSetType)
        .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
        .useCache(valueOrDefault(useCache, isSelect))
        .cache(currentCache);
   
    // parameterMap 以及被标记废弃 这里不做解释
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
    
    // 构建MappedStatement
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }
  
  // 上面用到的 获取ResultMap
  private List<ResultMap> getStatementResultMaps(
      String resultMap,
      Class<?> resultType,
      String statementId) {
    // 名称转换
    resultMap = applyCurrentNamespace(resultMap, true);
   
    // 结果集合
    List<ResultMap> resultMaps = new ArrayList<ResultMap>();
    if (resultMap != null) {
      // 可能多个
      String[] resultMapNames = resultMap.split(",");
      for (String resultMapName : resultMapNames) {
        try {
          // 从configuration中取出放到结果集里面
          resultMaps.add(configuration.getResultMap(resultMapName.trim()));
        } catch (IllegalArgumentException e) {
          throw new IncompleteElementException("Could not find result map " + resultMapName, e);
        }
      }
    } else if (resultType != null) {
      ResultMap inlineResultMap = new ResultMap.Builder(
          configuration,
          statementId + "-Inline",
          resultType,
          new ArrayList<ResultMapping>(),
          null).build();
      resultMaps.add(inlineResultMap);
    }
    return resultMaps;
  }

MappedStatement

上面会通过MappedStatement的静态内部类Build来创建,那么首先看一下MappenStatement的结构

public final class MappedStatement {
 
  // mapper配置文件名 例如: UserMapper.xml
  private String resource;
  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;
  private List<ResultMap> resultMaps;
  // 刷新缓存
  private boolean flushCacheRequired;
  // 是否使用缓存 默认true
  private boolean useCache;
  // 结果排序
  private boolean resultOrdered;
  //sql语句的类型,如select、update、delete、insert
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;

  MappedStatement() {
    // constructor disabled
  }
    
  // 后面省略.....
}

然后看一下其静态内部类

public static class Builder {
    // 创建对象MappedStatement
    private MappedStatement mappedStatement = new MappedStatement();
    
    // 构造方法
    public Builder(Configuration configuration, String id, SqlSource sqlSource, SqlCommandType sqlCommandType) {
      mappedStatement.configuration = configuration;
      mappedStatement.id = id;
      mappedStatement.sqlSource = sqlSource;
      mappedStatement.statementType = StatementType.PREPARED;
      mappedStatement.parameterMap = new ParameterMap.Builder(configuration, "defaultParameterMap", null, new ArrayList<ParameterMapping>()).build();
      mappedStatement.resultMaps = new ArrayList<ResultMap>();
      mappedStatement.sqlCommandType = sqlCommandType;
      mappedStatement.keyGenerator = configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
      String logId = id;
      if (configuration.getLogPrefix() != null) {
        logId = configuration.getLogPrefix() + id;
      }
      mappedStatement.statementLog = LogFactory.getLog(logId);
      mappedStatement.lang = configuration.getDefaultScriptingLanguageInstance();
    }
    
    // 建造方法
    public MappedStatement build() {
      // assert关键字就是断言 如果判断条件不成立 则结束执行 避免太多的if/else判断
      assert mappedStatement.configuration != null;
      assert mappedStatement.id != null;
      assert mappedStatement.sqlSource != null;
      assert mappedStatement.lang != null;
      // 设置只读
      mappedStatement.resultMaps = Collections.unmodifiableList(mappedStatement.resultMaps);
      return mappedStatement;
    }
 }
    

6.通过namespace解析mapper接口

接下来继续往下面介绍parse()中调用的bindMapperForNamespace(),该方法的功能就是判断当前映射文件中namespace对应的接口是否解析

我们在 mybatis-config.xml 中的mapper标签中可以配置扫描类或xml文件,如果只配置了xml文件,那么会通过namespace来获取接口,同时把接口加载进去

// 找出对应当前Mapper.xml的java类型,也就是我们的dao接口,并添加到Mybatis全局配置信息中
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);
          configuration.addMapper(boundType);
        }
      }
    }
  }

7.解析异常resultMap

在直接我们在configurationElement()中解析了resultMap标签,但是有发生异常的会被保留,这里再次进行解析,相当于在给你一次机会

//重新解析Mybatis全局配置信息中未能完成的ResultMap标签信息
private void parsePendingResultMaps() {
    // 获取Mybatis全局配置信息中未能完成的ResultMap标签信息
    // 也就是初步解析的时候发生异常的
    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...
          // 抛出异常时,不做任何操作,让未能完成的ResultMap标签信息继续在Mybatis全局配置信息中等待下次解析
        }
      }
    }
}

3.解析异常DML标签

在直接我们在configurationElement()中解析了DML标签,但是有发生异常的会被保留,这里再次进行解析

private void parsePendingStatements() {
    // 获取异常的sql
    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...
        }
      }
    }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

为人师表好少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值