[Hand tear MyBatis source code] MyBatis startup process, Configuration configuration system

MyBatis startup process

The general process is as follows:

  • Load configuration XML file
  • Read the dtd description file of mybatis and parse the xml tag
  • Generate the corresponding global configuration object by reading the XML configuration information, and generate the SQL mapping that requires mapper.
  • After creating SqlSessionFactory, use SqlSessionFactory to create Session.

insert image description here

  • Configuration: It is the core object of the Mybatis initialization process. Almost all configuration information in Mybatis will be saved in Configuration and take effect globally.
  • XMLConfigBuilder: used to create Configuration, parse the configuration information of the configuration node in the MyBatis configuration file and populate it into the Configuration object
  • XMLMapperEntityResolver: Included in XMLConfigBuilder, used to read local DTD files.
  • XPathParser: XPath parser, which encapsulates JDK classes and is included in XMLConfigBuilder. XPath is the XML Path Language (XML Path Language), which is a language used to determine the location of a certain part of an XML document.
  • Document : Included in XPathParser, it is convenient to read configuration file content later combined with XPathParser.

Create SQLSessionFactory

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

public SqlSessionFactory build(InputStream inputStream) {
    
    
    return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    
    
    // 准备阶段:将配置文件加载到内存中并生成document对象,然后创建初始化Configuration对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  // 解析document对象并生成 SqlSessionFactory
  //parser.parse()返回的就是一个Configuration对象,对应的就是下面的方法
    return build(parser.parse());
}

public SqlSessionFactory build(Configuration config) {
    
    
  return new DefaultSqlSessionFactory(config);
}

The SQLSessionFactory we created is actually DefaultSqlSessionFactory, and its creation requires a Configuration object

Create XMLConfigBuilder

Creating a SqlSessionFactory requires:

  • Rely on XMLConfigBuilder to read and parse configuration files, and convert configuration files into Document objects
  • Initializes the Configuration object and populates properties from the configuration file.
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    
    
   this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    
    
   // 调用父类初始化configuration
   super(new Configuration());
   ErrorContext.instance().resource("SQL Mapper Configuration");    
   // 将Properties全部设置到configuration里面去
   this.configuration.setVariables(props);
   this.parsed = false;
   this.environment = environment;
  // 绑定 XPathParser
   this.parser = parser;
}

When initializing Configuration, the class to be used and its alias will be registered in the typeAliases map.

private final Map<String, Class<?>> typeAliases = new HashMap<>();
public Configuration() {
    
    
  typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
  typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

  typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
  typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
  typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

  typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  typeAliasRegistry.registerAlias("LRU", LruCache.class);
  typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

  typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

  typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
  typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

  typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
  typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
  typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
  typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
  typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
  typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
  typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

  typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

  languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  languageRegistry.register(RawLanguageDriver.class);
}

Create XPathParser

XPathParser contains many important objects, including XMLMapperEntityResolver objects for reading local DTD files, XPath objects, Document objects for storing xml files, and key-value pair collection object variables defined by properties tags.

insert image description here

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    
    
  this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    
    
  // 初始化属性
  commonConstructor(validation, variables, entityResolver);
  // 读取xml文件,保存为 document
  this.document = createDocument(new InputSource(inputStream));
}
public class XPathParser {
    
    
  private final Document document;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;
}

Parse and set properties in Configuration

The configuration analysis of MyBatis is based <configuration>on the label

Let's look at a configuration file:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--SqlSessionFactoryBuilder中配置的配置文件的优先级最高;config.properties配置文件的优先级次之;properties标签中的配置优先级最低 -->
    <properties resource="org/mybatis/example/config.properties">
      <property name="username" value="dev_user"/>
      <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

    <!--一些重要的全局配置-->
    <settings>
    <setting name="cacheEnabled" value="true"/>
    <!--<setting name="lazyLoadingEnabled" value="true"/>-->
    <!--<setting name="multipleResultSetsEnabled" value="true"/>-->
    <!--<setting name="useColumnLabel" value="true"/>-->
    <!--<setting name="useGeneratedKeys" value="false"/>-->
    <!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
    <!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
    <!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
    <!--<setting name="defaultStatementTimeout" value="25"/>-->
    <!--<setting name="defaultFetchSize" value="100"/>-->
    <!--<setting name="safeRowBoundsEnabled" value="false"/>-->
    <!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
    <!--<setting name="localCacheScope" value="STATEMENT"/>-->
    <!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
    <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
    <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
    </settings>

    <typeAliases>

    </typeAliases>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果-->
            <!--如果某些查询数据量非常大,不应该允许查出所有数据-->
            <property name="pageSizeZero" value="true"/>
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://10.59.97.10:3308/windty"/>
                <property name="username" value="windty_opr"/>
                <property name="password" value="windty!234"/>
            </dataSource>
        </environment>
    </environments>

    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

    <mappers>
        <!--这边可以使用package和resource两种方式加载mapper-->
        <!--<package name="包名"/>-->
        <!--<mapper resource="./mappers/SysUserMapper.xml"/>-->
        <mapper resource="./mappers/CbondissuerMapper.xml"/>
    </mappers>

</configuration>

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

// XMLConfigBuilder
public Configuration parse() {
    
    
  // 根据parsed变量的值判断是否已经完成了对mybatis-config.xml配置文件的解析
  if (parsed) {
    
    
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  // 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

We continue into this parseConfiguration method:

private void parseConfiguration(XNode root) {
    
    
    try {
    
    
      //issue #117 read properties first
      //解析properties标签,并set到Configration对象中
      //在properties配置属性后,在Mybatis的配置文件中就可以使用${key}的形式使用了。
      propertiesElement(root.evalNode("properties"));
      
      //解析setting标签的配置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //添加vfs的自定义实现,这个功能不怎么用
      loadCustomVfs(settings);
        
      //配置类的别名,配置后就可以用别名来替代全限定名
      //mybatis默认设置了很多别名,参考附录部分
      typeAliasesElement(root.evalNode("typeAliases"));
        
      //解析拦截器和拦截器的属性,set到Configration的interceptorChain中
      //MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
      //Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
        //ParameterHandler (getParameterObject, setParameters)
        //ResultSetHandler (handleResultSets, handleOutputParameters)
        //StatementHandler (prepare, parameterize, batch, update, query)
      pluginElement(root.evalNode("plugins"));
      
      //Mybatis创建对象是会使用objectFactory来创建对象,一般情况下不会自己配置这个objectFactory,使用系统默认的objectFactory就好了
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
       
      //设置在setting标签中配置的配置
      settingsElement(settings);
   
      //解析环境信息,包括事物管理器和数据源,SqlSessionFactoryBuilder在解析时需要指定环境id,如果不指定的话,会选择默认的环境;
      //最后将这些信息set到Configration的Environment属性里面
      environmentsElement(root.evalNode("environments"));
        
      //
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        
      //无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。解析typeHandler。
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析Mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
    
    
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

The parsing of the configuration file requires the expression of xpath and the document object, matches the node according to the label name, takes out the corresponding value, finds the node and returns the XNode object, and then sets the corresponding value for Configuration

//XPathParser
public XNode evalNode(String expression) {
    
    
  return evalNode(document, expression);
}

Let's take a closer look at the analysis of several important nodes:

Parse the envirmonent node

For example in our configuration file:

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <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) {
    
    
    // 未指定XMLConfigBuilder.environment字段,则使用default属性
    if (environment == null) {
    
    
      environment = context.getStringAttribute("default");
    }
    // 遍历子节点
    for (XNode child : context.getChildren()) {
    
    
      String id = child.getStringAttribute("id");
      // 与XmlConfigBuilder.environment字段匹配
      if (isSpecifiedEnvironment(id)) {
    
    
        // 创建TransactionFactory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        // 创建DataSourceFactory和DataSource
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        // 创建Environment
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        // 将Environment对象记录到Configuration.environment字段中
        configuration.setEnvironment(environmentBuilder.build());
        break;
      }
    }
  }
}

Parsing Mappers Tags

<mappers>
    <package name="com.daley.dao"/>
</mappers>

If node name equals package

  • It will automatically scan all mappers under the package path,
  • The mapperRegistry in the configuration will add the corresponding package path
  • Add the MapperProxyFactory corresponding to the final mapper to knowMappers
private void mapperElement(XNode parent) throws Exception {
    
    
  if (parent != null) {
    
    
    // 处理mapper子节点
    for (XNode child : parent.getChildren()) {
    
    
      // package子节点
      if ("package".equals(child.getName())) {
    
    
        // 自动扫描包下所有映射器
        String mapperPackage = child.getStringAttribute("name");
        // 扫描指定的包,并向mapperRegistry注册mapper接口
        configuration.addMappers(mapperPackage);
      } else {
    
    
        
        // 直接指定Mapper属性
        // 获取mapper节点的resource、url、class属性,三个属性互斥
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        // 如果mapper节点指定了resource或者url属性,则创建XmlMapperBuilder对象,并通过该对象解析resource或者url属性指定的mapper配置文件
        if (resource != null && url == null && mapperClass == null) {
    
    
          // 使用类路径
          ErrorContext.instance().resource(resource);
          try(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路径
          ErrorContext.instance().resource(url);
          try(InputStream inputStream = Resources.getUrlAsStream(url)){
    
    
            // 创建XMLMapperBuilder对象,解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url == null && mapperClass != null) {
    
    
          // 如果mapper节点指定了class属性,则向MapperRegistry注册该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.");
        }
      }
    }
  }
}

insert image description here

//MapperRegistry
public void addMappers(String packageName) {
    
    
    addMappers(packageName, Object.class);
  }
public void addMappers(String packageName, Class<?> superType) {
    
    
  // 查找包下所有父类是 Objects.class 的类
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    
    
    addMapper(mapperClass);
  }
}
// MapperRegistry.addMapper()
public <T> void addMapper(Class<T> type) {
    
    
 ……
    boolean loadCompleted = false;
    // 将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
    knownMappers.put(type, new MapperProxyFactory<>(type));
    
    // 对被注解接口的解析逻辑
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    parser.parse();
    loadCompleted = true;
}

After adding the mapper mapping relationship, it will start to analyze the relevant annotations in the Mapper interface

  • The mapper xml file corresponding to the mapper class will be read, and the content in the file will be parsed after reading
  • For each method of the variable mapper class, read the marked annotations for analysis, such as resultMap
public void parse() {
    
    
  String resource = type.toString();
  // 检测是否已经加载过该接口
  if (!configuration.isResourceLoaded(resource)) {
    
    
    // 检测是否加载过对应的映射配置文件,如果未加载,则创建XMLMapperBuilder对象解析对应的mapper映射xml文件
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // 解析@CacheNamespace注解
    parseCache();
    // 解析@CacheNamespaceRef注解
    parseCacheRef();
    //开始解析每个方法
    for (Method method : type.getMethods()) {
    
    
      if (!canHaveStatement(method)) {
    
    
        continue;
      }
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
    
    
        parseResultMap(method);
      }
      try {
    
    
        // 解析@SelectKey,@ResultMap等注解,并创建MappedStatement对象
        parseStatement(method);
      } catch (IncompleteElementException e) {
    
    
        // 如果解析过程出现IncompleteElementException异常,可能是引用了未解析的注解,此处将出现异常的方法添加到incompleteMethod集合中保存
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  // 遍历incompleteMethods集合中记录的未解析的方法,并重新进行解析
  parsePendingMethods();
}

The loadXmlResource method will call the parse method to parse the content in the mapper of the object xml.

public void parse() {
    
    
  // 判断是否已经加载过该映射文件
  if (!configuration.isResourceLoaded(resource)) {
    
    
    // 处理mapper节点
    configurationElement(parser.evalNode("/mapper"));
    // 将resource添加到Configuration.loadedResources集合中保存,他是hashset类型的集合,其中记录了已经加载过的映射文件
    configuration.addLoadedResource(resource);
    // 绑定映射器到namespace
    bindMapperForNamespace();
  }

  // 处理ConfigurationElement方法中解析失败的resultMap节点
  parsePendingResultMaps();
  // 处理ConfigurationElement方法中解析失败的cache-ref节点
  parsePendingCacheRefs();
  // 处理ConfigurationElement方法中解析失败的SQL语句节点
  parsePendingStatements();
}

configurationElement method: parse nodes such as sql, resultMap, parameterMap, etc.:

private void configurationElement(XNode context) {
    
    
  try {
    
    
    // 获取mapper节点的namespace属性
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
    
    
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    // 设置MapperBuilderAssistant的currentNamespace字段,记录当前命名空间
    builderAssistant.setCurrentNamespace(namespace);
    // 解析cache-ref节点
    cacheRefElement(context.evalNode("cache-ref"));
    // 解析cache节点
    cacheElement(context.evalNode("cache"));
    // 解析parameterMap节点
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    // 解析resultMap节点
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // 解析sql节点
    sqlElement(context.evalNodes("/mapper/sql"));
    // 解析select、update、insert、delete等SQL节点
    // 就在这里开始创建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);
  }
}

So far, the Configuration has been created, and the created SqlSessionFactory will be returned. Let's summarize:

insert image description here

The process of openSession

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    
    
    Transaction tx = null;
    try {
    
    
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      //获取执行器,这边获得的执行器已经代理拦截器的功能(见下面代码)
      final Executor executor = configuration.newExecutor(tx, execType);
      //根据获取的执行器创建SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
    
    
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
    
    
      ErrorContext.instance().reset();
    }
  }
//interceptorChain生成代理类,具体参见Plugin这个类的方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    
    
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
    
    
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
    
    
      executor = new ReuseExecutor(this, transaction);
    } else {
    
    
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
    
    
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

Executor is divided into two categories, one is CacheExecutor, and the other is ordinary Executor.
Ordinary Executors are divided into three basic Executors, SimpleExecutor, ReuseExecutor, and BatchExecutor.

  • SimpleExecutor: Every time an update or select is executed, a Statement object is opened, and the Statement object is closed immediately when it is used up.
  • ReuseExecutor: Execute update or select, use sql as the key to find the Statement object, use it if it exists, and create it if it does not exist. After use, the Statement object will not be closed, but placed in Map<String, Statement> for the next use. In short, the Statement object is reused.
  • BatchExecutor: Execute update (no select, JDBC batch processing does not support select), add all sql to the batch processing (addBatch()), wait for unified execution (executeBatch()), it caches multiple Statement objects, and each Statement object waits to execute the executeBatch() batch processing one by one after addBatch() is completed. Same as JDBC batching.
    Scope of action: These features of Executor are strictly limited within the scope of the SqlSession life cycle.

CacheExecutor actually encapsulates ordinary Executor. The difference from ordinary Executor is that it first checks whether there is a result in the cache before querying. If it exists, it uses the result in the cache. If it does not exist, it still uses ordinary Executor to query, and then stores the query result in the cache.

insert image description here

Configuration overview

Configuration is the centralized management center of the entire MyBatis configuration system. Most of the components mentioned above, such as Executor, StatementHandler, Cache, MappedStatement, etc., are directly or indirectly created and managed by it. In addition, the property configuration that affects the behavior of these components is also saved and maintained by it. Such as cacheEnabled, lazyLoadingEnabled...etc. So it is very image of the big housekeeper of MyBatis .

Configuration is an extremely important class in MyBatis, which represents the global configuration information of MyBatis.

Configuration contains a lot of key information, mainly including:

  1. environments: store multiple environment information, each environment has transactionFactory and dataSource;
  2. mappers: stores the location information of all mapper files;
  3. mappedStatements: stores all mappedStatement information, each mappedStatement represents a SQL statement;
  4. resultMaps: stores all resultMap information, and resultMap represents the result set mapping;
  5. parameterMap: used by the old version, deprecated;
  6. keyGenerators: stores all keyGenerator information, and keyGenerator is used to generate primary keys;
  7. typeHandlers: stores all typeHandler information, typeHandler is used for conversion between javaType and jdbcType;
  8. objectFactory: object factory, used to instantiate the target object;
  9. objectWrapperFactory: object wrapper factory, used to create proxy objects for target objects;
  10. Interceptors: Stores all Interceptor information, and Interceptor is used for interceptors to execute SQL;
  11. databaseIdProvider: Get the corresponding mappedStatement according to databaseId;
  12. logImpl: log factory;
  13. cache: cache namespace;
  14. etc.

All the information saved by Configuration constitutes the entire configuration environment of a MyBatis session (SqlSession). Every time a new SqlSession is opened, all the information needed by the SqlSession will be constructed according to the Configuration .

So in short, Configuration stores the global configuration information of MyBatis, representing a MyBatis configuration environment, which includes information such as SQL mapping statements, result set mapping, caching mechanism, etc., which constitute all the configuration content required by a MyBatis session.

  • When we call SqlSessionFactoryBuilder.build(reader)build SqlSessionFactory,The bottom layer will build a Configuration object according to the configuration file

  • When we call to openSession()open a session, SqlSession consists of a Configuration and an Executor. This Configuration is the global configuration information built earlier.

So it can be seen that Configuration is of great significance to MyBatis, which contains all the configuration resources and environment information required by MyBatis sessions. Without Configuration, SqlSession cannot be built at all, and it cannot work.

The core role and configuration source of Configuration

Configuration There are three configuration sources:

  • Mybatis-config.xml startup file, global configuration, and global components are all derived from this.
  • Mapper.xml SQL mapping (MappedStatement)\result set mapping (ResultMapper) all come from here.
  • Another form of expression for @Annotation SQL mapping and result set mapping.

insert image description here

Then let's talk about the core role of Configuration:

  • Store global configuration information, which comes from settings (settings)
  • Initialize and maintain global base components
    • typeAliases (type aliases)
    • typeHandlers (type handlers)
    • plugins
    • environments (environment configuration)
    • cache (secondary cache space)
  • Initialize and maintain MappedStatement
  • Component builder, enhanced with plugins
    • newExecutor (executor)
    • newStatementHandler (JDBC handler)
    • newResultSetHandler (result set handler)
    • newParameterHandler (parameter handler)

Why do we use Configuration to create Executor here? The reasons are as follows:

1. Create according to the type of configuration

2. Whether to enable the cache

3. Use interceptorChain to introduce plug-ins

This achieves unified packaging and standardized creation of components.
insert image description here

The same is true for the creation of several other components:

insert image description here

Let's talk about a few components:

mapperRegistry

Used to register the mapper interface and generate its dynamic proxy object.
caches

Cache, application-level cross-session, all caches will be placed in Configuration after loading. Then mappedStatement will do an association operation with our cache, and there is a 1:1 relationship between them.

TypeAliasRegistry

TypeAliasRegistry is a registry for storing type aliases in MyBatis.
In MyBatis, we can use <typeAlias>tags to configure aliases for Java types, such as:

<typeAlias type="com.someapp.model.User" alias="User"/>

This will make User an alias of the com.someapp.model.User type, and User can be used directly to represent that type in subsequent mapping files.

So, how does MyBatis implement this type alias function?

This depends on TypeAliasRegistry

  • All type alias configurations are saved in TypeAliasRegistry. When our SQL mapping file is parsed, MyBatis will look for the <typeAlias>configuration and register the found type alias to the registry.
  • Then, in the subsequent parsing process, whenever MyBatis encounters a type, it will first check whether the TypeAliasRegistry has an alias for the type, and if it exists, use the alias, otherwise use the fully qualified name.

Therefore, the role of TypeAliasRegistry is to save the type alias configuration and allow MyBatis to find and use these aliases when needed . It implements the type alias function of MyBatis.

As one of the core components of MyBatis, TypeAliasRegistry mainly has the following functions:

  1. Save type alias configuration: MyBatis can register the type alias parsed from the mapping file in it for subsequent lookup and use.
  2. Find an alias: During the parsing process of MyBatis, whenever a type is needed, MyBatis will first look up the alias of the type in the TypeAliasRegistry. If present, use the alias, otherwise use the fully qualified name.
  3. Simplified configuration: Through type aliases, the mapping configuration of MyBatis can be simplified. We don't need to enter its full qualified name every time we use a type, we only need to use the alias configured by MyBatis, which improves the readability and efficiency of the configuration.
  4. Reduce the number of parsing: Every time MyBatis uses an alias instead of a fully qualified name, it can reduce the parsing of that package once, which can slightly improve the parsing performance of MyBatis.

configuration element

Configuration configuration information comes from xml and annotations. Each file and annotation is composed of several configuration elements and presents a nested relationship. The overall relationship is shown in the following figure:insert image description here

insert image description here

For the use of each configuration, please refer to the documentation given on the official website: https://mybatis.org/mybatis-3/zh/configuration.html#properties
insert image description here

element bearer

Whether it is XML or my annotations, these configuration elements must be converted into JAVA configuration attributes or object components to carry them. The corresponding relationship is as follows:

  • The full configuration (config.xml) is carried by the Configuration object property
  • sql mapping <select|insert...>or @Selectetc. is carried by MappedStatement object
  • Cache <cache..>or @CacheNamespacehosted by a Cache object
  • The result set map is hosted by the ResultMap object
    insert image description here

Configuration file parsing

XML file parsing process

Configuration file parsing requires us to discuss it separately, first to analyze the XML parsing process. The bottom layer of xml configuration parsing uses dom4j to first parse into a node tree, and then matches different parsers according to different node types. Finally parsed into specific components.

The base class of the parser is BaseBuilder, which contains a global Configuration object. The purpose of this is that all components to be parsed must be collectively attributed to Configuration. Let's take a look at what each parser does:

  • XMLConfigBuilder: Parsing the config.xml file will directly create a Configuration object for parsing the global configuration.
  • XMLMapperBuilder: Parse Mapper.xml file, content contains, etc.
  • MapperBuilderAssistant: Mapper.xml parsing assistant. In a Mapper.xml, Cache is shared with Statement (sql statement), and the distribution of shared components is realized by this parsing.
  • XMLStatementBuilder: SQL mapping parsing means <select|update|insert|delete> elements are parsed into MapperStatement.
  • SqlSourceBuilder: Sql data source parsing, which parses the declared SQL into executable SQL.
  • XMLScriptBuilder: Parse the set of SqlNode scripts set in the dynamic SQL data source.

insert image description here

The overall parsing process starts from XmlConfigBuilder, and then gradually parses inward until all nodes are parsed. We can understand its overall parsing process through a MappedStatement parsing process.

insert image description here

Flow Description:

  • 【XmlConfigBuilder】 Receive a config.xml input stream, and then create an empty Configuration object
    insert image description here

  • [XmlConfigBuilder] Parse the global configuration
    insert image description here

  • [XmlConfigBuilder] mapperElements analysis, specify the mapper.xml file through Resource or url

    • 【XmlMapperBuilder】Public configuration such as parsing cache and result set configuration
    • [XmlMapperBuilder] Parse Sql mapping <select|insert|upate|delete>
      • [XMLScriptBuilder] Parse and generate SQL data sources, including dynamic scripts
    • [XmlMapperBuilder] Build Statement
      • [MapperBuilderAssistant] Set the cache and add it to Configuration

Annotation configuration analysis

The underlying implementation of annotation parsing is achieved by obtaining annotation elements in the Mapper interface through reflection. There are two ways:

  • One is to directly specify the interface name
  • One is to specify the package name and then automatically scan all interface classes under the package

These logics are implemented by the Mapper Registry (MapperRegistry). It receives an interface class parameter, and creates a dynamic proxy factory for the interface based on the parameter, then parses the internal method annotations to generate each MapperStatement, and finally adds it to Configuration to complete the analysis.

insert image description here

If you want to know more, please refer to the article:
MyBatis Core Configuration Overview-Configuration Detailed Explanation of
Mybatis Main Configuration—Configuration

Guess you like

Origin blog.csdn.net/zyb18507175502/article/details/131105326