[Mybatis] - configuration initialization process

Mybatis configuration initialization process

Test code

SqlMapConfig.xml

<?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>
    <properties resource="db.properties"/>
    <typeAliases>
        <package name="com.zhiwei.entity"/>
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.user}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="advanced/StudentMapper.xml"/>
    </mappers>
</configuration>

StudentMapper.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.zhiwei.advanced.mapper.StudentMapper">
<resultMap type="com.zhiwei.entity.Student" id="studentResultMap">
    <id column="sid" property="sid"/>
    <result column="stuname" property="stuName"/>
    <result column="stupasswd" property="stuPasswd"/>
    <result column="stuage" property="stuAge"/>
    <result column="stugender" property="stuGender" javaType="java.lang.String"/>
    <result column="stuscore" property="stuScore" javaType="java.lang.Integer"/>
</resultMap>

<resultMap type="com.zhiwei.advanced.pojo.StudentCustomer" id="studentCustomerResultMap">
    <id column="sid" property="sid"/>
    <result column="sid" property="uid"/>
    <result column="stupasswd" property="passwd"/>
    <result column="stuname" property="stuName"/>
    <result column="stupasswd" property="stuPasswd"/>
    <result column="stuage" property="stuAge"/>
    <result column="stugender" property="stuGender" javaType="java.lang.String"/>
    <result column="stuscore" property="stuScore" javaType="java.lang.Integer"/>
</resultMap>

<select id="findStudentById" parameterType="int" resultMap="studentResultMap">
	select * from student where sid = #{sid}
</select>
</mapper>

Student

package com.zhiwei.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {

    private int sid;
    private String stuName;
    private String stuPasswd;
    private int stuAge;
    private String stuGender;
    private float stuScore;
}

StudentMapper

package com.zhiwei.advanced.mapper;

import com.zhiwei.advanced.pojo.StudentCustomer;
import com.zhiwei.advanced.pojo.StudentQueryVo;
import com.zhiwei.entity.Student;
import java.util.List;

public interface StudentMapper {

    public Student findStudentById(Integer sid);
}

Startup class

sqlSession.getMapper(StudentMapper.class).findStudentById(id);

Workflow

Parse the XML map to org.apache.ibatis.session.Configuration object, and create a sessionfactory based on it. The Session created by sessionfactory is bound to the Executor and executed by the Executor

Configure the loading process

SessionFactory build code

Inputstream is = Resources.getResourceAsStream("SqlMapConfig.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);

SessionFactory build

org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)

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

XMLConfigBuilder tool class: mainly parsing SqlMapConfig.xml

 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;
  }

new Configuration(): default population configuration: similar to cache alias mapping class

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);
  }

XML parsing: parser.parse()

parsed parsing flag. The underlying layer calls parseConfiguration(parser.evalNode("/configuration")); Parsing SqlMapConfig.xml root node according to Xpath syntax

private void parseConfiguration(XNode root) {
    try {
    
     // Parse the properties tag of SqlMapConfig.xml, and the configuration.setVariables(defaults) parse the properties of the properties file to the configuration cache
      propertiesElement(root.evalNode("properties"));
      
      // setting configuration: configuration.setVfsImpl(vfsImpl)
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);

      //Alias configuration: configuration.typeAliasRegistry
      //DTD constraint: mybatis-3-config.dtd
      //package: key: class simple name hump nomenclature, class: class fully qualified name
      //type alias: key: alias
      //Note: it's better not to directly refer to the package form, otherwise the class with the same name in multiple packages will conflict
      typeAliasesElement(root.evalNode("typeAliases"));

      //Plug in configuration: it is equivalent to that the interceptor can customize operations, such as paging, CRUD operation interception, similar to AOP: configuration.interceptorChain
      pluginElement(root.evalNode("plugins"));

      //Set object factory: Oh, configuration.objectFactory
      objectFactoryElement(root.evalNode("objectFactory"));

      //Social group objectmapper factory: configuration.objectWrapperFactory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));

      //Set reflector factory: configuration.reflectorFactory, reflection mechanism operation
      reflectorFactoryElement(root.evalNode("reflectorFactory"));

      //mybatis global configuration: if there is no customization, the default configuration will be used
      settingsElement(settings);

      //Setting environment: configuration.environment, essentially caching the current working database information
      //Note: Environment can set multiple database environments, and specify specific databases through the default attribute
      environmentsElement(root.evalNode("environments"));

      //Specify the database ID of mybatis work: database product name
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));

      //Type processor: parse specific Mapper data type: configuration.typeHandlerRegistry
      //Function: analysis and transformation of database operation Mapper
      typeHandlerElement(root.evalNode("typeHandlers"));

      //Mapperprocessing: configuration.mapper registry
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

Mapper label parsing

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {

        //Batch configuration in package form: common
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        
        //mapper single stand-alone configuration
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder 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.");
          }
        }
      }
    }
  }

mapperParser.parse(); parsing

//Analytical process
public void parse() {

    //Determine whether StudentMapper.xml is loaded
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    //There may be problems in XML parsing and repeated processing. For example, the child ResultMap loads before the parent ResultMap
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

configurationElement(parser.evalNode("/mapper")); resolves Mapper label

 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);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));

      //Parsing parameterMap tag: configuration.parameterMaps
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));

     //Parsing the resultMap tag: configuration.resultMaps
      resultMapElements(context.evalNodes("/mapper/resultMap"));

      //Parsing sql Tags: configuration.sqlFragments
      sqlElement(context.evalNodes("/mapper/sql"));

      //Parsing: CRUD label mapped to MapperStatement
      //Analysis: build sql raw materials ParameterMaps, ResuldMap, sql fragments and namespace after preparation
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

Building MapperStatement

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);
      }
    }
  }

statementParser.parseStatementNode(); resolves to MapperStatement

 public void parseStatementNode() {
   String id = context.getStringAttribute("id");
   String databaseId = context.getStringAttribute("databaseId");

   if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
     return;
   }

   Integer fetchSize = context.getIntAttribute("fetchSize");
   Integer timeout = context.getIntAttribute("timeout");
   String parameterMap = context.getStringAttribute("parameterMap");
   String parameterType = context.getStringAttribute("parameterType");
   Class<?> parameterTypeClass = resolveClass(parameterType);
   String resultMap = context.getStringAttribute("resultMap");
   String resultType = context.getStringAttribute("resultType");
   String lang = context.getStringAttribute("lang");
   LanguageDriver langDriver = getLanguageDriver(lang);

   Class<?> resultTypeClass = resolveClass(resultType);
   String resultSetType = context.getStringAttribute("resultSetType");
   StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
   ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

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

   //Fill in the include refid reference sql segment
   XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
   includeParser.applyIncludes(context.getNode());

   //Parsing mybatis selectkey primary key to generate policy label
   processSelectKeyNodes(id, parameterTypeClass, langDriver);
   
   // Parsing sql
   SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
   String resultSets = context.getStringAttribute("resultSets");
   String keyProperty = context.getStringAttribute("keyProperty");
   String keyColumn = context.getStringAttribute("keyColumn");
   KeyGenerator keyGenerator;

   //MapperStatementId generation policy: namespace + id +! Operation type key
   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;
   }

   builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
       fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
       resultSetTypeEnum, flushCache, useCache, resultOrdered, 
       keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
 }

configuration add MapperStatement

 public MappedStatement addMappedStatement(
     String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang, String resultSets) {

   if (unresolvedCacheRef) {
     throw new IncompleteElementException("Cache-ref not yet resolved");
   }

   id = applyCurrentNamespace(id, false);
   boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

   MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
       .resource(resource)
       .fetchSize(fetchSize)
       .timeout(timeout)
       .statementType(statementType)
       .keyGenerator(keyGenerator)
       .keyProperty(keyProperty)
       .keyColumn(keyColumn)
       .databaseId(databaseId)
       .lang(lang)
       .resultOrdered(resultOrdered)
       .resultSets(resultSets)
       .resultMaps(getStatementResultMaps(resultMap, resultType, id))
       .resultSetType(resultSetType)
       .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
       .useCache(valueOrDefault(useCache, isSelect))
       .cache(currentCache);

   ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
   if (statementParameterMap != null) {
     statementBuilder.parameterMap(statementParameterMap);
   }

   MappedStatement statement = statementBuilder.build();
   configuration.addMappedStatement(statement);
   return statement;
 }

Binding namespace: bindMapperForNamespace();

org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace

Nature: the namespace is bound to Mapper interface to facilitate the subsequent generation of agent classes through Mapper operation

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);

          //mapperRegistry: save agent factory class
          configuration.addMapper(boundType);
        }
      }
    }
  }

Now MapperStatement initialization is complete

Tags: xml Mybatis Java SQL

Posted on Tue, 03 Dec 2019 06:29:11 -0500 by beckstei