花里胡哨系列之Mybatis源码分析---映射文件解析(三)

一:映射文件介绍

   

MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。简单说:就是写SQL代码的地方。

SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterMap – 老式风格的参数映射。此元素已被废弃,并可能在将来被移除!请使用行内参数映射。文档中不会介绍此元素。
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

二:映射文件解析流程

    映射文件解析是包含在配置文件解析过程中的,在上一章节有提到的,即配置文件解析篇(https://blog.csdn.net/qq_41961316/article/details/107403237)。

 1.在配置文件中配置映射文件(映射器)的常见配置

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

  2.常见的映射文件

  

<mapper namespace="org.apache.ibatis.mytest.MyDao">


 <insert id="insertAuthor" parameterType="org.apache.ibatis.domain.blog.Author">
  insert into Author (id,username,password,email,bio)
  values (#{id},#{username},#{password},#{email},#{bio})
 </insert>

  <select id="selectAll" parameterType="java.lang.String" resultType="java.util.HashMap">
     select * from Author where id = #{id}
  </select>

</mapper>

  3.映射文件解析流程

    在配置文件解析过程中,解析映射文件

   XMLConfigBuilder:

private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    //解析properties标签
    propertiesElement(root.evalNode("properties"));
    //解析setting配置,并转换为properties
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    //加载vfs?
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    // 解析 typeAliases 配置
    typeAliasesElement(root.evalNode("typeAliases"));
    // 解析 plugins 配置
    pluginElement(root.evalNode("plugins"));
    // 解析 objectFactory 配置
    objectFactoryElement(root.evalNode("objectFactory"));
    // 解析 objectWrapperFactory 配置
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    // 解析 reflectorFactory 配置
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // settings 中的信息设置到 Configuration 对象中
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    // 解析 environments 配置
    environmentsElement(root.evalNode("environments"));
    // 解析 databaseIdProvider,获取并设置 databaseId 到 Configuration 对象
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 解析 typeHandlers 配置
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 解析 mappers 配置
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}
/**
 * 解析mapper配置文件 .xml
 * @param parent
 * @throws Exception
 */
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        // 获取 <package> 节点中的 name 属性
        String mapperPackage = child.getStringAttribute("name");
        // 从指定包中查找 mapper 接口,并根据 mapper 接口解析映射配置
        configuration.addMappers(mapperPackage);
      } else {
        //resource,url,class 只能有一个
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          ErrorContext.instance().resource(url);
          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.");
        }
      }
    }
  }
}

  3.1先来分析根据配置的包路径,如何解析映射文件,如以下配置:

<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
Configuration:
public void addMappers(String packageName) {
  mapperRegistry.addMappers(packageName);
}
MapperRegistry:
public void addMappers(String packageName) {
  addMappers(packageName, Object.class);
}
public void addMappers(String packageName, Class<?> superType) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  //通过 VFS(虚拟文件系统) 获取包下面的所有文件,上一章有说过
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  //获取文件集合
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    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 {
      //将接口 传进去,方便创建代理对象,这一步是连接dao层接口和 映射器配置文件 的 桥梁
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // 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);
      //解析xml文件,以及 type接口上的方法注解
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

  接下来通过 MapperAnnotationBuilder这个类解析 xml中的信息,已经接口层的注解,如@CacheNamespace,@Options

MapperAnnotationBuilder:
public void parse() {
  String resource = type.toString();
  //检查是否已经解析过映射文件
  if (!configuration.isResourceLoaded(resource)) {
    //解析映射器xml文件
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    //解析接口中的缓存注解 @CacheNamespace
    parseCache();
    //解析接口中的共享缓存注解 @CacheNamespaceRef
    parseCacheRef();
    Method[] methods = type.getMethods();
    for (Method method : methods) {
      try {
        // issue #237
        if (!method.isBridge()) {
          //解析接口上面 方法的注解
          parseStatement(method);
        }
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  parsePendingMethods();
}
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())) {
    /**
     * 根据接口名 获取 xml文件路径,这里可以看出
     * <mappers>
     *   <package name="org.mybatis.builder"/>
     * </mappers>
     * 这种包扫描方式,扫描的是 该路径下的接口,
     * 如果在接口路径下 ,存在接口对应的  xml映射文件,那么才能解析到xml映射文件
     * 如: org.apache.test.MyTest.class
     *     org/apache/test/MyTest.xml
     *
     * <mappers>
     *   <mapper class="org.mybatis.builder.AuthorMapper"/>
     *   <mapper class="org.mybatis.builder.BlogMapper"/>
     *   <mapper class="org.mybatis.builder.PostMapper"/>
     * </mappers>
     * 这种方式 也是同理
     */
    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) {
      //将 输入流 转为 Document对象
      XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
      //解析 Document对象
      xmlParser.parse();
    }
  }
}

看看XMLMapperBuilder具体解析 xml映射文件过程,分为3个步骤

  1.解析xml文件,主要解析 cache,cache-ref,resultMap,sql,select|insert|update|delete 这些标签

  2.根据xml中配置的命名空间,绑定相应的接口(namespace)

  3.对于解析xml过程中有异常的,进行补偿处理

首先,看看解析 xml文件解析步骤

XMLMapperBuilder:
public void parse() {
  //检查resource是否被解析过
  if (!configuration.isResourceLoaded(resource)) {
    // 解析 mapper 节点
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //通过命名空间绑定接口,这一步并没有创建代理对象
    bindMapperForNamespace();
  }
  //处理未完成解析的节点,resultMap,cache-ref,statements
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}
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> 节点
    //在 MyBatis 中,二级缓存是可以共用的。这需要通过<cache-ref>节点为命名空间配置参
    //照缓存,引用其他命名空间的缓存,在一个mapper.xml文件中,cache 和 cache-ref一般只会存在一个
    //如果都存在,看具体解析的顺序
    cacheRefElement(context.evalNode("cache-ref"));
    //解析<cache> 节点,二级缓存,在解析 select|insert|update|delete 会将二级缓存设置进去
    cacheElement(context.evalNode("cache"));
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    sqlElement(context.evalNodes("/mapper/sql"));
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

  先看 cache,再看cache-ref

private void cacheElement(XNode context) {
  if (context != null) {
    String type = context.getStringAttribute("type", "PERPETUAL");
    Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
    String eviction = context.getStringAttribute("eviction", "LRU");
    Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
    Long flushInterval = context.getLongAttribute("flushInterval");
    Integer size = context.getIntAttribute("size");
    boolean readWrite = !context.getBooleanAttribute("readOnly", false);
    boolean blocking = context.getBooleanAttribute("blocking", false);
    //获取properties子节点,结合第三方缓存
    Properties props = context.getChildrenAsProperties();
    //构建缓存对象,缓存用到了装饰者模式,自行分析
    builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
  }
}

 cache-ref

/**
 *  Mapper1 与 Mapper2 共用一个二级缓存
 * <cache-ref namespace="xyz.coolblog.dao.Mapper2"/>
 * @param context
 */
private void cacheRefElement(XNode context) {
  if (context != null) {
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
     //应用 引用的缓存到当前xml映射器
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      //捕捉异常,添加到未完成 map,后续步骤会继续尝试添加
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}
CacheRefResolver:
public Cache resolveCacheRef() {
  return assistant.useCacheRef(cacheRefNamespace);
}
MapperBuilderAssistant:
public Cache useCacheRef(String namespace) {
  if (namespace == null) {
    throw new BuilderException("cache-ref element requires a namespace attribute.");
  }
  try {
    //后续构造 MappedStatement对象时判断该变量,true表明 Cache-ref已经解析成功
    //false表明失败,MappedStatement也创建失败,抛异常(补偿措施)
    unresolvedCacheRef = true;
    // 根据命名空间从全局配置对象(Configuration)中查找相应的缓存实例
    Cache cache = configuration.getCache(namespace);
    /*
     * 若未查找到缓存实例,此处抛出异常。这里存在两种情况导致未查找到
     * cache 实例,分别如下:
     * 1.使用者在 <cache-ref> 中配置了一个不存在的命名空间,
     * 导致无法找到 cache 实例
     * 2.使用者所引用的缓存实例还未创建(后续步骤会补全),和xml加载顺序相关
     */
    if (cache == null) {
      throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
    }
    //设置当前 xml映射文件的 缓存对象
    currentCache = cache;
    unresolvedCacheRef = false;
    return cache;
  } catch (IllegalArgumentException e) {
    throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
  }
}
resultMap:结果映射,建议调试几遍,容易理解
XMLMapperBuilder:
private void resultMapElements(List<XNode> list) throws Exception {
  for (XNode resultMapNode : list) {
    try {//解析单个 <resultMap></resultMap>标签
      resultMapElement(resultMapNode);
    } catch (IncompleteElementException e) {
      // ignore, it will be retried
    }
  }
}
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
  return resultMapElement(resultMapNode, Collections.emptyList(), null);
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {
  ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
  //解析type属性
  String type = resultMapNode.getStringAttribute("type",
      resultMapNode.getStringAttribute("ofType",
          resultMapNode.getStringAttribute("resultType",
              resultMapNode.getStringAttribute("javaType"))));
  Class<?> typeClass = resolveClass(type);
  if (typeClass == null) {
    typeClass = inheritEnclosingType(resultMapNode, enclosingType);
  }
  Discriminator discriminator = null;
  List<ResultMapping> resultMappings = new ArrayList<>();
  resultMappings.addAll(additionalResultMappings);
  List<XNode> resultChildren = resultMapNode.getChildren();
  for (XNode resultChild : resultChildren) {//解析子标签<result> </result>,<id>
    if ("constructor".equals(resultChild.getName())) {
      processConstructorElement(resultChild, typeClass, resultMappings);
    } else if ("discriminator".equals(resultChild.getName())) {
      //discriminator 根据数据库返回结果值,决定引用 哪个resultMap
      discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
    } else {
      List<ResultFlag> flags = new ArrayList<>();
      if ("id".equals(resultChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      //解析id和type,  创建ResultMapping,获取<result> </result>标签中的所有属性
      resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
    }
  }
  //解析id属性
  String id = resultMapNode.getStringAttribute("id",
          resultMapNode.getValueBasedIdentifier());
  String extend = resultMapNode.getStringAttribute("extends");
  Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
  ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
  try {
    // 根据前面获取到的信息构建 ResultMap 对象, id --> ResultMap
    return resultMapResolver.resolve();
  } catch (IncompleteElementException  e) {
    configuration.addIncompleteResultMap(resultMapResolver);
    throw e;
  }
}

 解析查询语句:select|insert|update|delete

XMLMapperBuilder
private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      //开始解析
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}
XMLStatementBuilder:
public void parseStatementNode() {
  String id = context.getStringAttribute("id");
  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,解析include标签
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());

  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);

  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);

  // Parse selectKey after includes and remove them.
  //解析<selectKey>标签
  // <selectKey keyProperty="id" resultType="int" order="BEFORE">
  //      select author_seq.nextval from dual
  //  </selectKey>
  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;
  }
  //解析sql语句
  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");
  //通过别名解析 resultType 对应的类型
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultMap = context.getStringAttribute("resultMap");
  String resultSetType = context.getStringAttribute("resultSetType");
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  if (resultSetTypeEnum == null) {
    resultSetTypeEnum = configuration.getDefaultResultSetType();
  }
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  String resultSets = context.getStringAttribute("resultSets");
  //创建 MappedStatement,包含了sql的所有信息,如sql语句,入参,结果映射,缓存, 保存到configuration中
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

主要看一下解析sql:SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);

XMLLanguageDriver:
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
  //会初始化一些 nodeHandler,处理动态节点,if ,where等等
  XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
  return builder.parseScriptNode();
}
XMLScriptBuilder:
public SqlSource parseScriptNode() {
  //解析sql语句,以及包含的动态标签
  MixedSqlNode rootSqlNode = parseDynamicTags(context);
  SqlSource sqlSource;
  if (isDynamic) {
    //这种类型的sql,后续执行sql时,需要将继续解析,因为sql语句依赖输入参数
    sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
  } else {
    //这一步会将 rootSqlNode里面contents集合拼接成完整字符串,
    //同时将 #{}转为 ?,并保存参数信息
    sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
  }
  return sqlSource;
}
protected MixedSqlNode parseDynamicTags(XNode node) {
  List<SqlNode> contents = new ArrayList<>();
  NodeList children = node.getNode().getChildNodes();
  for (int i = 0; i < children.getLength(); i++) {
    XNode child = node.newXNode(children.item(i));//获取下一个完整标签
    //判断标签的type,是否只包含 text(没有动态标签)
    if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
      String data = child.getStringBody("");
      TextSqlNode textSqlNode = new TextSqlNode(data);
      if (textSqlNode.isDynamic()) {//检查text中是否包含 ${} 这种字符串,是说明是动态的
        contents.add(textSqlNode);
        //动态节点标志位
        isDynamic = true;
      } else {
        contents.add(new StaticTextSqlNode(data));
      }
    } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) {//标签元素,如set,if等等// issue #628
      String nodeName = child.getNode().getNodeName();
      //获取对应的动态节点handler,在初始化步骤添加了
      NodeHandler handler = nodeHandlerMap.get(nodeName);
      if (handler == null) {
        throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
      }
      //用 各种动态节点NodeHandler 解析 XNode,将结果添加到 contents
      handler.handleNode(child, contents);
      //动态节点标志位
      isDynamic = true;
    }
  }
  return new MixedSqlNode(contents);
}

    xml映射文件解析,最终每个select,insert,update,delete 都会生成一个 MappedStatement,包含了所有信息,如sql语句,入参,结果映射,缓存, 保存到configuration中

   然后,看看 根据xml中配置的命名空间,绑定相应的接口(namespace)步骤

XMLMapperBuilder
private void bindMapperForNamespace() {
  //获取命名空间,解析xml时以保存
  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);
        //绑定 namespace对应的接口,上面已经讲过
        configuration.addMapper(boundType);
      }
    }
  }
}
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 {
      //将接口 传进去,方便创建代理对象,这一步是连接dao层接口和 映射器配置文件 的 桥梁
      knownMappers.put(type, new MapperProxyFactory<>(type));
      // 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);
      //解析xml文件,以及 type接口上的方法注解
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

  最后,看看 对于解析xml过程中有异常的,如何进行补偿处理

public void parse() {
  //检查resource是否被解析过
  if (!configuration.isResourceLoaded(resource)) {
    // 解析 mapper 节点
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //通过命名空间绑定接口,这一步并没有创建代理对象
    bindMapperForNamespace();
  }
  //处理未完成解析的节点,resultMap,cache-ref,statements
  parsePendingResultMaps();
  //cacheRef异常
  parsePendingCacheRefs();
  parsePendingStatements();
}
private void parsePendingCacheRefs() {
  //在捕获异常的时候,添加相应的异常信息
  Collection<CacheRefResolver> incompleteCacheRefs = configuration.getIncompleteCacheRefs();
  synchronized (incompleteCacheRefs) {
    Iterator<CacheRefResolver> iter = incompleteCacheRefs.iterator();
    while (iter.hasNext()) {
      try {
        //在已解析过的 cache标签中寻找 cacheRef对应的 namespace 引用cache,
        iter.next().resolveCacheRef();
        iter.remove();
      } catch (IncompleteElementException e) {
        // Cache ref is still missing a resource...
      }
    }
  }
}

 在解析 cacheRef标签时,出现异常,进行捕获

private void cacheRefElement(XNode context) {
  if (context != null) {
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
      //
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      //捕捉异常,添加到未完成 map,后续步骤会继续尝试添加
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}

    xml映射文件解析过程大致如上,其中,对于sql 动态节点的解析需要多调试,因为确实不太好说。。

剩下的其他配置映射文件的方式,如 

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>

 解析过程的代码都是大致相同的,自行调试

4.总结

    映射文件解析过程相对复杂,尤其是结果映射中的嵌套映射,sql语句中的动态节点解析,需要多调试。

    接下去将进入到真正的 执行sql流程,有兴趣可以关注。

    最后,来一张映射文件解析流程图:

  

结语:大风起兮云飞扬,安得猛士兮走四方

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值