2.6、mybatis源码分析之初始化过程(1)

  • 前面介绍了myabtis的基础组件部分,感觉起来非常的零散,没有系统性。但是前面部分的内容是基础,了解了前面基础组件部分的内容,在跟着流程去看源码,就不会云里雾里了,如果不看前面的基础,蒙着头往mybatis源码里面去撞,基本会迷路摸不清方向。下面我们将跟着myabtis的执行流程去分析源码,首先要分析的是mybatis的初始化过程。
  • 在将mybatis初始化过程之前,先来看看mybatis的的简单执行代码。
//开始初始化
String resource = "mybatis-config.xml";  
InputStream inputStream = Resources.getResourceAsStream(resource);  
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder()
                      .build(inputStream); 
//获取会话并开始执行sql 
SqlSession sqlSession = sqlSessionFactory.openSession();  
List list = sqlSession.selectList("com.huya.qiu.mapper.UserMapper.getAllUser");

一、mybatis初始化过程

  • *在mybatis初始化过程中主要完成的是config配置、mapper配置和相关注解信息的解析,初始化入口就是SqlSessionFactoryBuilder.buidler()方法。
  public SqlSessionFactory build(Reader reader, String environment, 
Properties properties) {
    try {
     1、读取配置
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, 
environment, properties);
      2、解析配置文件得到Configuration对象,创建DefaultSqlSessionFactory对象
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

从上面可以看出,build方法主要做的就是两件事(1)创建XMLConfigBuilder 对象(2)解析配置文件得到Configuration对象,创建DefaultSqlSessionFactory对象。下面针对这两个步骤进行详细的分析。

1、XMLConfigBuilder(解析核心配置文件)

  • 扮演具体建造者的角色,主要负责解析config配置文件的解析并继承BaseBuilder类。
    1、BaseBuilder
    是mybatis中有关建造者的抽象类,扮演着建造者模式中的建造者接口角色,具体的类结构如图,BaseBuilder拥有众多的实现类,这些实现类都扮演着具体场合的建造者角色:


    BaseBuilder类结构图
public abstract class BaseBuilder {
//引用configuration对象
  protected final Configuration configuration;
  //TypeAliasRegistry会记录别名
  protected final TypeAliasRegistry typeAliasRegistry;
  //TypeHandlerRegistry加注册类型转换器(自定义或者默认)
  protected final TypeHandlerRegistry typeHandlerRegistry;

  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
  ...提供了一系列的resolveTypeHandler、resolveResultSetType等抽象方法

2、XMLConfigBuilder具体实现

public class XMLConfigBuilder extends BaseBuilder {
    //标志是否已经解析过配置文件
  private boolean parsed;
  //用于加息配置文件的XpathParse对象
  private final XPathParser parser;
  //标签
  private String environment;
  //负责创建和缓存Reflector对象
  private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
//构造函数
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
//解析config配置文件的入口
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //查找节点,开始解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  
  private void parseConfiguration(XNode root) {
    try {
      //解析节点
      propertiesElement(root.evalNode("properties"));
      //解析节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      //解析节点
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析节点
      pluginElement(root.evalNode("plugins"));
      //解析节点
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析节点
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //解析节点
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);//将值设置到setting中
      //解析节点
      environmentsElement(root.evalNode("environments"));
      //解析节点
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析节点
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析节点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
  • parseConfiguration方法是最终执行配置文件解析的地方,从方法中可以看出解析的顺序是按着核心配置文件中节点配置顺序来的,因此如果我们在配置核心配置文件的时候,是要按着节点顺序来的。下面针对方法中每一个节点的解析详细介绍:
    (1)解析节点
  • 解析后形成Properties对象,之后将该字段设置到XpathParser和Configuration的variables字段中。
private void propertiesElement(XNode context) throws Exception {
    if (context != null) {
    //解析相应的name和value属性记录到properties中
      Properties defaults = context.getChildrenAsProperties();
      //解析reource和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.");
      }
      if (resource != null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }
      //更新xpathParse和configuration的variables字段
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

(2)解析节点

  • settings设置会改变mybatis的运行时行为,在初始化的时候这些全局配置信息都会被记录到configuration对象的对应属性中
private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    //解析settings子节点的name和value属性并返回properties对象
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    //创建metaClass
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
    //检测key指定的属性在configuration类中是都有对应的setter方法
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }

(3)解析解析节点

  • typeAliases与别名有关,前面知识可知解析后在TypeAliasRegistry中注册。
private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
      //处理package节点
        if ("package".equals(child.getName())) {
        //获取包名
          String typeAliasPackage = child.getStringAttribute("name");
          //注册包下的所有别名,放入hash中
          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);
          }
        }
      }
    }
  }

(4)解析节点

  • 通过TypeHandlerRegistry对象完成TypeHandler的注册实现原理和上面的相似

(5)解析节点

  • 插件是mybatis提供的扩展机制之一,用于通过添加自定义的插件对sql语句执行过程中某一个点进行拦截。自定义插件需要实现Interceptor接口并通过注解指定需要拦截的方法签名
 private void pluginElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
      //获取节点的属性的值
        String interceptor = child.getStringAttribute("interceptor");
        //获取节点下配置信息形成properties对象
        Properties properties = child.getChildrenAsProperties();
        //进行属性别名注册,并实例化拦截器对象
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        //设置属性
        interceptorInstance.setProperties(properties);
        //加载到configuration
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }
  protected Class resolveClass(String alias) {
    if (alias == null) {
      return null;
    }
    try {
      return resolveAlias(alias);
    } catch (Exception e) {
      throw new BuilderException("Error resolving class. Cause: " + e, e);
    }
  }
   protected Class resolveAlias(String alias) {
    return typeAliasRegistry.resolveAlias(alias);
  }

(6)解析等节点

  • 逻辑和解析节点差不多

(7)解析节点

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
      //未指定enviroment字段则就只用default指定
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        //与environment字段匹配
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
         //创建datasourcefactory和datasource
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          //建造者模式
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
              //记录到environment字段中
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }
  private boolean isSpecifiedEnvironment(String id) {
    if (environment == null) {
      throw new BuilderException("No environment specified.");
    } else if (id == null) {
      throw new BuilderException("Environment requires an id attribute.");
    } else if (environment.equals(id)) {
      return true;
    }
    return false;
  }

(8)解析节点

  • 可以通过databaseIdProvider定义所支持的数据库产品databaseId,然后在映射配置文件中定义sql语句节点时候,通过databaseid指定该sql语句应用的数据库产品。实现帮助开发人员屏蔽多种数据库产品在sql语句方面的差异。
  • mybatis中根据datasource确定数据库产品类型,然后在解析映射配置文件时候,加载不带databaseId属性和带有匹配当前数据库databaseId属性的所有sql语句,如果同时找到name抛弃不带databaseId的相同语句。
 private void databaseIdProviderElement(XNode context) throws Exception {
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
      String type = context.getStringAttribute("type");
      // awful patch to keep backward compatibility
      if ("VENDOR".equals(type)) {
          type = "DB_VENDOR";
      }
      Properties properties = context.getChildrenAsProperties();
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    if (environment != null && databaseIdProvider != null) {
    //通过datasource获取databaseId,记录到configuration.databaseId字段中
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

(9)解析节点

  • 在加载config配置文件时候,节点节点会告诉mybatis去哪些位置查找映射配置文件以及使用配置注解标识的接口
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
      //package子节点
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          //向mapperRegistry注册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,解析映射配置文件
            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.");
          }
        }
      }
    }
  }

二、XMLMapperBuilder(解析映射配置文件)

  • 负责解析映射配置文件,继承了BaseBuilder抽象类。也是具体的建造者角色,parse方法是其入口
public void parse() {
//判断是否已经加载了该映射文件
    if (!configuration.isResourceLoaded(resource)) {
    //处理节点
      configurationElement(parser.evalNode("/mapper"));
      //将resource添加到集合中保存,记录加载过的映射文件
      configuration.addLoadedResource(resource);
      //注册mapper接口
      bindMapperForNamespace();
    }
//处理configurationElement中解析失败的节点
    parsePendingResultMaps();
    //处理configurationElement中解析失败的节点
    parsePendingCacheRefs();
    //处理configurationElement中解析失败的sql语句节点
    parsePendingStatements();
  }
  //封装了每一个节点的解析过程
private void configurationElement(XNode context) {
    try {
    //1.获取节点的namespace属性,并记录命名空间
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //2.解析cache-ref节点
      cacheRefElement(context.evalNode("cache-ref"));
      //3.解析cache节点
      cacheElement(context.evalNode("cache"));
      //已经废弃
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //4.解析resultMap节点
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //5.解析sql节点
      sqlElement(context.evalNodes("/mapper/sql"));
      //6.解析select|insert|update|delete等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);
    }
  }
  • 从方法中可以得出配置文件的解析最终也是归属到单个节点的解析
    (1)解析cache节点
  • mybatis默认没有开启二级缓存,要开启需要在映射配置文件中添加cache节点
private void cacheElement(XNode context) throws Exception {
    if (context != null) {
    //获取cache节点的type属性,默认是PERPETUAL
      String type = context.getStringAttribute("type", "PERPETUAL");
      //查找type属性对应的cache接口欧
      Class typeClass = typeAliasRegistry.resolveAlias(type);
      //获取eviction属性默认是LRU
      String eviction = context.getStringAttribute("eviction", "LRU");
      //解析eviction属性指定的cache装饰器类型
      Class evictionClass = 
typeAliasRegistry.resolveAlias(eviction);
      //获取flushInterval属性默认是null
      Long flushInterval = context.getLongAttribute("flushInterval");
      //获取size属性值
      Integer size = context.getIntAttribute("size");
      //获取readOnly属性值
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      //通过builderAssistant创建Cache对象,并添加到caches集合中保存
      builderAssistant.useNewCache(typeClass, evictionClass, 
flushInterval, size, readWrite, blocking, props);
    }
  }
------------------------------------------------------------------------------
builderAssistant类中的useNewCache方法
  public Cache useNewCache(Class typeClass,
      Class 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;
  }

(2)解析cache-ref

  • 如果想实现一个二级缓存在多个namespace之间共用可以配置cache-ref节点
    (3)解析resultMap节点
  • resultMap定义了结果集与javabean之间的映射规则
    ResultMap对象

每一个resultMap节点会被映射成一个ResultMap对象,而每一个节点所定义的映射关系使用ResultMapping对象表示
也就是说一个ResultMap对应resultMap节点,ResultMapping对应resultMap属性

public class ResultMap {
  private Configuration configuration;

  private String id;//节点id
  private Class type;//节点类型
  private List resultMappings;//其他映射关系集合
  private List idResultMappings;//记录映射关系带有id标识的映射关系,id节点和constructor节点的idArg子节点
  private List constructorResultMappings;//constructor所有子节点
  private List propertyResultMappings;//不带有constructor标识的映射关系
  private Set mappedColumns;//记录所有涉及column属性的集合
  private Set mappedProperties;//记录所有propertes属性的集合
  private Discriminator discriminator;//鉴别器节点
  private boolean hasNestedResultMaps;//含有resultMap属性不含有resultSet属性则为true
  private boolean hasNestedQueries;//存在select属性,为true
  private Boolean autoMapping;//是否开启自动映射
...

resultMapping对象

  • 记录了结果集中的一列与JavaBean中的一个属性之间的映射关系,也对应resultMap中的属性
public class ResultMapping {

  private Configuration configuration;
  private String property;//对应节点的property属性,表示的是该列进行映射的属性
  private String column;//对应节点的column属性,表示从数据库中得到的列名或者列名的别名
  private Class javaType;//对应节点的javaType属性,表示的是一个JavaBean的完全限定名,后者一个类型别名
  private JdbcType jdbcType;//对应节点的jdbcType属性,表示映射列的JDBC类型
  private TypeHandler typeHandler;//对应节点的typeHandler,表示类型处理器
  private String nestedResultMapId;//对应节点的resultMap属性,通过id引用另一个resultMap节点的定义
  private String nestedQueryId;//对应节点的select属性,通过id应用到另一个select节点定义
  private Set notNullColumns;
  private String columnPrefix;
  private List flags;
  private List composites;
  private String resultSet;//对应resultSet属性
  private String foreignColumn;
  private boolean lazy;//是否延迟加载,对应fetchType属性
...

resultMapElements方法解析节点

 private void resultMapElements(List list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }
  //处理每一个resultMap节点
private ResultMap resultMapElement(XNode resultMapNode, List additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //获取id属性,默认会拼装所有父节点的id或者value后者Property属性值
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
        //获取type属性,表示结果集将被映射成type指定类型的对象(也就是如果有ofType、resultType等最后映射成type)
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
     //后去extends属性,指定继承关系
    String extend = resultMapNode.getStringAttribute("extends");
    //是否是自动映射,及自动查找与列名同名的属性名
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    //加息type类型
    Class typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List resultMappings = new ArrayList();
    resultMappings.addAll(additionalResultMappings);
    //处理resultMap子节点
    List resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
    //处理constructor子节点
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
      //处理id、result、association、collection等子节点
        List flags = new ArrayList();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        //创建ResultMapping对象,添加到resultmappings集合中保存
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
    //创建ResultMap对象,添加到Configuration.resultmaps集合中去
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
//创建相应的ResultMapping对象
private ResultMapping buildResultMappingFromContext(XNode context, Class resultType, List flags) throws Exception {
    String property;
    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. 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> typeHandlerClass = (Class>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }

整个解析过程还是很复杂的,建议去看源码,这里就不贴代码了(ps:自己这一部分也是看的半懂,后面有时间回看一遍)

(4)sql节点解析

  • 使用sql节点可以增加sql语句片段的重复利用,只需要使用include引入就好了
 private void sqlElement(List list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {//遍历sql节点
    //获取databaseiId
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      //为id添加命名空间
      id = builderAssistant.applyCurrentNamespace(id, false);
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
      //记录到sqlFragments集合中保存
        sqlFragments.put(id, context);
      }
    }
  }

(5)sql语句相关节点解析

待定...


你可能感兴趣的:(2.6、mybatis源码分析之初始化过程(1))