Mybatis源码分析-1 加载配置文件

390 阅读9分钟

介绍:MyBatis是一流的持久性框架,支持自定义SQL,存储过程和高级映射。MyBatis消除了几乎所有的JDBC代码以及参数的手动设置和结果检索。MyBatis可以使用简单的XML或注释进行配置,并将映射接口和Java POJO(普通的旧Java对象)映射到数据库记录。

开始看源码:

public static void main(String[] args) throws IOException {
    // 加载配置文件
    InputStream inputStream = Resources.getResourceAsStream("config.xml");
    
    // 解析配置文件构建SqlsessionFactory,本篇文章就是从这个地方开始展开。//只看这一句话
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    // 创建 defaultSqlSession 对象
    SqlSession session = sqlSessionFactory.openSession();
    // 执行查询
    UserMapper mapper = session.getMapper(UserMapper.class);
    LogLogin logLogin = mapper.selectByPrimaryKey(1);
    System.out.println(logLogin.toString());
}
// 根据配置文件的输入流,构建配置类对象
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null); // 继续跟进
}
// 和主要逻辑没有关系的代码先删掉了。减少干扰
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 解析xml文件,转换成XMLConfigBuilder对象,不展开了,就是解析xml文件
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // ★ parser.parse() 解析加载Mybatis相关的XML配置文件 mapper  连接信息等
      // build 构建SqlSessionFactory
      return build(parser.parse()); // 这俩方法展开看一下
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession .", e);
    } finally {
      ErrorContext.instance().reset();
      // 关闭流代码省略
    }
}
/** 解析配置文件 */

// 标识是否解析过配置文件
private boolean parsed;

public Configuration parse() {
    // 确保文件只能解析一次
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true; // 解析一次之后,改为true
    
    // 真正解析配置文件,赋值给配置类 Configuration,该类是MyBatis的主要配置文件类
    // parser.evalNode("/configuration") 解析configuration节点
    parseConfiguration(parser.evalNode("/configuration")); // 下面来看parseConfiguration()
    return configuration;
}
/**
* 真正解析配置文件,赋值给配置类Configuration
* 获取xml的节点属性,存入配置类, 数据库连接,用户名 密码 mapper  关系映射等等
*/
private void parseConfiguration(XNode root) {
    try {
        // 解析 properties 节点
        propertiesElement(root.evalNode("properties"));
        // 解析 settings 节点
        Properties settings = settingsAsProperties(root.evalNode("settings"));
        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"));
        settingsElement(settings);
        // ★ 数据库类型 帐号密码 配置  可参照下图看一下👇
        environmentsElement(root.evalNode("environments"));
        // 解析 databaseIdProvider 节点
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        // 解析 typeHandlers 节点
        typeHandlerElement(root.evalNode("typeHandlers"));
        // ★  xml sql语句加载  可参照下图看一下👇
        mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}


下面是把配置信息载入Configuration的每个节点的相关逻辑

第一步:解析properties节点: propertiesElement(root.evalNode("properties"));

带着这段xml看会好一些

<properties resource="org/mybatis/example/config.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
        // 获取子dom
        Properties defaults = context.getChildrenAsProperties();
        
        // 获取properties的  resource & url 俩兄弟属性不能同时存在
        String resource = context.getStringAttribute("resource");
        String url = context.getStringAttribute("url");
        if (resource != null && url != null) {
            throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
        }
        
        // 俩兄弟谁在都行   Properties其实底层就是个HashTable,putAll()就是hashtable的方法
        if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
        } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
        }
        // configuration.getVariables()返回一个Properties 的对象,variables是Configuration的一个属性
        // 很多配置文件都是存在这个属性里的,这里判断下,之前存过属性的话,就需要合并一下
        Properties vars = configuration.getVariables();
        if (vars != null) {
            defaults.putAll(vars);
        }
        // 更新Variables字段
        parser.setVariables(defaults);
        // 重新赋值。
        configuration.setVariables(defaults);
    }
}

第二步:解析 settings 节点
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);

xml示例

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
      .....
</settings>

// 虚拟文件系统  用来查找指定路径 下的资源
private void loadCustomVfs(Properties props) throws ClassNotFoundException {
    String value = props.getProperty("vfsImpl");
    if (value != null) {
        String[] clazzes = value.split(",");
        for (String clazz : clazzes) {
        if (!clazz.isEmpty()) {
            @SuppressWarnings("unchecked")
            Class<? extends VFS> vfsImpl = (Class<? extends VFS>)Resources.classForName(clazz);
            configuration.setVfsImpl(vfsImpl);
        }
        }
    }
}
// 加载自定义 LogImpl
private void loadCustomLogImpl(Properties props) {
    Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
    configuration.setLogImpl(logImpl);
}

第三步:解析 typeAliases 节点 typeAliasesElement(root.evalNode("typeAliases"));

typeAliases 的 xml示例:

1
<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
      .....
</typeAliases>
2
<typeAliases>
  <package name="domain.blog"/>
</typeAliases>
// 没啥可说的了 ,大同小异
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
                String typeAliasPackage = child.getStringAttribute("name");
                configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
            } else {
                String alias = child.getStringAttribute("alias");
                String type = child.getStringAttribute("type");
                try {
                    Class<?> clazz = Resources.classForName(type);
                    if (alias == null) {
                        typeAliasRegistry.registerAlias(clazz);
                    } else {
                        typeAliasRegistry.registerAlias(alias, clazz);
                    }
                } catch (ClassNotFoundException e) {
                    throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
                }
            }
        }
    }
}

第三步:解析 plugins 节点 pluginElement(root.evalNode("plugins"));
这里挺重要的。 sql的拦截器都是通过这个来进行实现的,后面会说到执行。

plugins 的 xml示例: 配置自定义插件

<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>  -- 传的参数,插件中可以获取到
  </plugin>
</plugins>
private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
        for (XNode child : parent.getChildren()) {
            // 获取插件对象全路径
            String interceptor = child.getStringAttribute("interceptor");
            // 获取参数
            Properties properties = child.getChildrenAsProperties();
            // 通过反射创建插件(Mybatis的拦截器)实例对象
            Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
            // 设置参数
            interceptorInstance.setProperties(properties);
            // 把拦截器加入主配置对象里面
            configuration.addInterceptor(interceptorInstance);
        }
    }
}

第四步:解析objectFactory节点 objectFactoryElement(root.evalNode("objectFactory"));

objectFactory的xml示例

<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>
// 先获取工厂类,通过反射创建实例,赋值放入主配置文件   和上面的插件加载逻辑一样,换个名字。  
private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        Properties properties = context.getChildrenAsProperties();
        ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        factory.setProperties(properties);
        configuration.setObjectFactory(factory);
    }
}

第五步:解析 objectWrapperFactory 节点 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

// 还是一样的逻辑  和ObjectFactory一样
private void objectWrapperFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        configuration.setObjectWrapperFactory(factory);
    }
}

第六步:解析 reflectorFactory 节点 reflectorFactoryElement(root.evalNode("reflectorFactory"));

// 又来一个。。。
private void reflectorFactoryElement(XNode context) throws Exception {
    if (context != null) {
        String type = context.getStringAttribute("type");
        ReflectorFactory factory = (ReflectorFactory) resolveClass(type).getDeclaredConstructor().newInstance();
        configuration.setReflectorFactory(factory);
    }
}

第七步: 一大堆配置来袭

private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
}

第八步: 解析 environments 节点 environmentsElement(root.evalNode("environments"));
这个是大家都熟悉的东西了, >用户名 密码 数据库连接等

environments 的xml 示例

<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <dataSource type="POOLED">
      <property name="driver" value="${driver}"/>
      <property name="url" value="${url}"/>
      <property name="username" value="${username}"/>
      <property name="password" value="${password}"/>
    </dataSource>
  </environment>
</environments>
// 数据库类型、连接信息 帐号密码配置
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            // 根据default设置的属性值,选取加载environment id为属性值的那一个配置
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            String id = child.getStringAttribute("id");
            // 判断需要加载的配置  通过environment判断,这个是XMLConfigBuilder中的属性,string类型,不是主配置configuration中的
            if (isSpecifiedEnvironment(id)) {
              // 事务工厂
              TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
              // 数据源工厂对象
              DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
              // 数据源对象
              DataSource dataSource = dsFactory.getDataSource();
              // 这里是主配置中的Environment了  👇 下面简单了解下这个类属性
              Environment.Builder environmentBuilder = new Environment.Builder(id)
                  .transactionFactory(txFactory)
                  .dataSource(dataSource);
              configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}
public final class Environment { // 存储环境信息的类
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
}

第九步:解析 databaseIdProvider 节点

xml 示例

<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>
// 选择使用的数据库
private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
        String type = context.getStringAttribute("type");
        // 为了版本兼容 myabtis这里做了兼容处理, 很像我写的代码。-_-
        if ("VENDOR".equals(type)) {
            type = "DB_VENDOR";
        }
        // 获取属性,反射创建DatabaseIdProvider对象
        Properties properties = context.getChildrenAsProperties();
        databaseIdProvider = (DatabaseIdProvider) resolveClass(type).getDeclaredConstructor().newInstance();
        databaseIdProvider.setProperties(properties);
    }
    // 主配置赋值
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
        String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
        configuration.setDatabaseId(databaseId);
    }
}

第十步: 解析 typeHandlers 节点 typeHandlerElement(root.evalNode("typeHandlers"));

xml示例

<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>
<typeHandlers>
  <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
    javaType="java.math.RoundingMode"/>
</typeHandlers>
<typeHandlers>
  <package name="org.mybatis.example"/>
</typeHandlers>
private void typeHandlerElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          String javaTypeName = child.getStringAttribute("javaType");
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          String handlerTypeName = child.getStringAttribute("handler");
          Class<?> javaTypeClass = resolveClass(javaTypeName);
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          Class<?> typeHandlerClass = resolveClass(handlerTypeName);
          if (javaTypeClass != null) {
            if (jdbcType == null) {
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

第十一步:xml sql语句加载 mapperElement(root.evalNode("mappers"));

xml 示例

<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<mappers>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
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 类型的配置
          // URL 和 resource 这个一样,只看一个
          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();
          // url 类型的配置
          } 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();
          // mapperClass 类型的配置
          } else if (resource == null && url == null && mapperClass != null) {
            // 反射加载mapper对象
            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.");
          }
        }
      }
    }
  }
// 构建XMLMapperBuilder对象
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }
public void parse() {
    // 判断是否加载过,没加载的话执行后面逻辑
    if (!configuration.isResourceLoaded(resource)) {
      // 处理mapper sql语句的xml  👇下面看代码
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}
<mapper namespace="com.mybatis.study.LogMapper">
  <resultMap id="BaseResultMap" type="com.mybatis.study.Log">
    <id column="id" jdbcType="INTEGER" property="id" />
    <result column="username" jdbcType="VARCHAR" property="username" />
  </resultMap>
  <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultMap="BaseResultMap">
    select * from log where id = #{id,jdbcType=INTEGER}
  </select>
</mapper>
private void configurationElement(XNode context) {
    try {
    // 熟悉的namespace,,不能为空
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      // 缓存相关的配置
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      // 1️⃣ 参数映射(已废弃,不推荐使用)  这四块代码又很多。 看吧继续。👇下面代码
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 2️⃣ 结果映射
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // 3️⃣ sql语句
      sqlElement(context.evalNodes("/mapper/sql"));
      // 4️⃣ 根据语句类型构建
      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);
    }
  }

这次先到这里吧,sql语句的解析在写篇文章放,这已经太多了。。

官方说明了已废弃,不再说了。 弃用!老式的参数映射方法。内联参数是首选的,这个元素将来可能会被删除。此处没有文档记录。

1️⃣参数处理
private void parameterMapElement(List<XNode> list) {
    for (XNode parameterMapNode : list) {
      String id = parameterMapNode.getStringAttribute("id");
      String type = parameterMapNode.getStringAttribute("type");
      Class<?> parameterClass = resolveClass(type);
      List<XNode> parameterNodes = parameterMapNode.evalNodes("parameter");
      List<ParameterMapping> parameterMappings = new ArrayList<>();
      for (XNode parameterNode : parameterNodes) {
        String property = parameterNode.getStringAttribute("property");
        String javaType = parameterNode.getStringAttribute("javaType");
        String jdbcType = parameterNode.getStringAttribute("jdbcType");
        String resultMap = parameterNode.getStringAttribute("resultMap");
        String mode = parameterNode.getStringAttribute("mode");
        String typeHandler = parameterNode.getStringAttribute("typeHandler");
        Integer numericScale = parameterNode.getIntAttribute("numericScale");
        ParameterMode modeEnum = resolveParameterMode(mode);
        Class<?> javaTypeClass = resolveClass(javaType);
        JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
        Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
        ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
        parameterMappings.add(parameterMapping);
      }
      builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    }
  }
2️⃣结果映射
private void resultMapElements(List<XNode> list) {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }
3️⃣sql语句
private void sqlElement(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }
4️⃣ 根据语句类型构建
private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
        buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
}