Public search “code road mark”, point of concern not lost!

Configuration stores all the Configuration information required by the mybatis runtime. How is it converted from mybatis-config.xml? What does it do in practice? Today I will explore the Configuration parsing process step by step through a small example combined with the source code, in order to have a deeper understanding of its operation mechanism.

Starting from the Demo

Here is a small example. SqlSessionFactory = SqlSessionFactoryBuilder; SqlSession = Mapper;

  public static void main(String[] args) throws IOException {
      // Mybatis configuration file
      String path = "mybatis-config.xml";
      InputStream inputStream = Resources.getResourceAsStream(path);
      / / get SqlSessionFactory
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
      / / create a SqlSession
      try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
          CompanyDao dao = sqlSession.getMapper(CompanyDao.class);
          CompanyDO companyDO = dao.selectById(1."test"); System.out.println(companyDO); }}Copy the code

We don’t see Configuration in the main method, because in the actual run, the database is manipulated using SqlSession, which is created by SqlSessionFactory on demand. The Configuration object is stored in the SqlSessionFactory and is passed to the SqlSession when it is created.

Configuration And Resolution Process

Lines 3-6 of the sample code complete the Configuration parsing in the method SqlSessionFactoryBuilder#build(), which inside reveals that the actual XML parsing is done by the class XMLConfigBuilder.

  public SqlSessionFactory build(InputStream inputStream) {
    // Call overloaded methods
    return build(inputStream, null.null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // Create the XMLConfigBuilder object, which is the Configuration parsing class
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // First parse the XML into a Configuration object, then create an SqlSessionFactory object.
      // We'll focus on the parse() method next
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.}}}Copy the code

In Mybatis, XMLConfigBuilder is responsible for parsing Mybatis -config. XML and realizing the conversion and mapping of configuration information from files to memory objects. Let’s first look at the constructor.

  // Receives the profile stream object, the environment, and the property configuration passed in through the code.
  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) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }
Copy the code

The XMLConfigBuilder constructor creates an XML parser that handles complex XML reading. In addition, the constructor receives props objects, which override property information of the same name that was parsed from the configuration file. The parse() method and parseConfiguration() are the backbone of the parse process. ParseConfiguration parses each part of the XML one by one, and each configuration is packaged as a separate method that is easy to understand. Each XMLConfigBuilder can be parsed only once, and repeated parses cause an exception.

  public Configuration parse(a) {
    // Can only parse once, otherwise exception
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // mark as resolved
    parsed = true;
    // Parse the contents under the Configuration node
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // Parse attributes
      propertiesElement(root.evalNode("properties"));
      // Parse the configuration
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      / / load the VFS
      loadCustomVfs(settings);
      // Load custom logs
      loadCustomLogImpl(settings);
      // Load the type alias
      typeAliasesElement(root.evalNode("typeAliases"));
      // Load the plug-in
      pluginElement(root.evalNode("plugins"));
      // Load the object factory
      objectFactoryElement(root.evalNode("objectFactory"));
      // Load the object wrapper factory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      // Load reflector factory
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      // Set the configuration information
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      // Load the environment configuration
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // Load the type handler
      typeHandlerElement(root.evalNode("typeHandlers"));
      / / load mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code

The loading method for each of the above configurations internally assigns a value to Configuration. All configurations are finally parsed and returned to the caller by the parse method. The following describes the configuration parsing process of each part of MyBatis.

Properties

Used to define property information needed to run MyBatis, such as database connection configuration. Attributes can be defined in three ways: Define the properties in mybatis-config.xml#properties, through the external properties file, And then use the resource modes into mybatis – config. XML, through the SqlSessionFactoryBuilder is. Build () method is introduced into property values.

  // The entry parameter is root.evalNode("properties"), which is all the children of the properties node
  private void propertiesElement(XNode context) throws Exception {
    if(context ! =null) {
      // Read property information configured in all child nodes. All information is stored in defaults
      Properties defaults = context.getChildrenAsProperties();
      // If the external configuration is imported in resource or URL mode, the configuration information in resource or URL is loaded
      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.");
      }
      // Write the external configuration to defaults. Note that the external configuration overwrites the configuration information read from properties above
      if(resource ! =null) {
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if(url ! =null) {
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      // Reads configuration information passed in code from Configuration and writes it to defaults, if any.
      // The configuration of the same name loaded in the previous two steps will also be overwritten
      Properties vars = configuration.getVariables();
      if(vars ! =null) {
        defaults.putAll(vars);
      }
      // Attributes are parsed and stored.parser.setVariables(defaults); configuration.setVariables(defaults); }}Copy the code

If the same attribute is configured repeatedly in different locations, MyBatis will be loaded in the following order:

  • The properties specified in the body of the Properties element are first read.
  • The properties file is then read from the classpath based on the Resource attribute in the Properties element, or from the path specified by the URL attribute, overwriting previously read properties of the same name.
  • The property passed as a method parameter is finally read, overwriting previously read properties of the same name.

Settings

Set the item

These are very important tuning Settings in MyBatis, and they change the runtime behavior of MyBatis. The following code + comments explain each setting and its purpose. Most of the Settings have default values that can actually be changed as needed.

public class Configuration {

    // Environment configuration
    protected Environment environment;

    // Whether paging is allowed in nested statements (RowBounds). Default is false
    protected boolean safeRowBoundsEnabled;

    // Whether result handlers (ResultHandler) are allowed in nested statements. The default is true
    protected boolean safeResultHandlerEnabled = true;

    // Whether to enable automatic mapping of camel names, that is, from the classic database column name A_COLUMN to the classic Java property name aColumn.
    protected boolean mapUnderscoreToCamelCase;

    // When enabled, calls to either method load all lazy-loaded properties of the object. Otherwise, each lazy-loaded attribute is loaded on demand.
    protected boolean aggressiveLazyLoading;

    // Whether a single statement is allowed to return multiple result sets (requires database driver support). The default value is true
    protected boolean multipleResultSetsEnabled = true;

    // Allow JDBC support for automatic primary key generation, which requires database driver support. If set to true, automatic primary key generation is enforced.
    // Although some database drivers do not support this feature, it still works (Derby, for example).
    protected boolean useGeneratedKeys;

    // Use column labels instead of column names. Actual performance depends on database drivers, which can be observed by referring to the database driver documentation or by comparing tests.
    protected boolean useColumnLabel = true;

    // Globally turns on or off any caches configured in all mapper configuration files. Default is true
    protected boolean cacheEnabled = true;

    // Specifies whether setter (put for map objects) methods are called when the result set value is null. This is useful when initialization depends on map.keyset () or null values.
    // Note that primitive types (int, Boolean, etc.) cannot be set to null.
    / / default is false
    protected boolean callSettersOnNulls;

    // Allow the use of names in method signatures as statement parameter names.
    To use this feature, your project must be compiled in Java 8 with the -parameters option.
    // The default is true
    protected boolean useActualParamName = true;

    MyBatis returns null by default when all columns of the returned row are empty. When this setting is enabled, MyBatis returns an empty instance.
    // Note that it also applies to nested result sets (such as collections or associations). The default is false
    protected boolean returnInstanceForEmptyRow;

    // Specify the prefix MyBatis added to the log name. The default is no
    protected String logPrefix;

    // Specify a specific implementation of the log used by MyBatis. If not specified, it will be automatically found.
    //SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
    / / no by default
    protected Class <? extends Log> logImpl;

    // Specify the VFS implementation
    protected Class <? extends VFS> vfsImpl;

    //MyBatis uses Local caching to prevent circular references and speed up repeated nested queries.
    // The default value is SESSION, which caches all queries executed in a SESSION. If the value is set to STATEMENT, the local cache will only be used to execute statements. Different queries of the same SqlSession will not be cached.
    / / optional value: the SESSION | STATEMENT
    protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;

    // The default JDBC type for null values when no specific JDBC type is specified for the parameter.
    // Some database drivers need to specify the JDBC type of a column. In most cases, just use the generic type, such as NULL, VARCHAR, or OTHER.
    // Default value: OTHER
    protected JdbcType jdbcTypeForNull = JdbcType.OTHER;

    // Specify which methods of the object trigger a lazy load. A comma-separated list of methods.
    / / such as equals, clone, hashCode, and toString
    protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals"."clone"."hashCode"."toString"));

    // Sets the timeout period, which determines the number of seconds for the database driver to wait for the database response. The default value is not set.
    protected Integer defaultStatementTimeout;

    // Set a recommended value for the driver's result set fetchSize. This parameter can only be overridden in query Settings. Default not set
    protected Integer defaultFetchSize;

    // Configure the default actuator. SIMPLE is a plain actuator; The REUSE executor reuses preparedStatements. The BATCH executor not only reuses statements but also performs BATCH updates.
    // SIMPLE, REUSE, BATCH. Default value: SIMPLE
    protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

    // Specify how MyBatis should automatically map columns to fields or attributes.
    // NONE disables automatic mapping.
    // PARTIAL only automatically maps fields that do not define nested result mappings.
    // FULL automatically maps any complex result set (whether nested or not).
    // Default value: PARTIAL
    protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;

    // Specifies the behavior of discovering unknown columns (or unknown attribute types) for the auto-mapping target.
    //NONE: does nothing
    / / WARNING: output WARNING logs (' org. Apache. Ibatis. Session. AutoMappingUnknownColumnBehavior 'log level should be set to WARN)
    //FAILING: mapping failure (throws SqlSessionException)
    // Default value: NONE
    protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

    // Attribute list
    protected Properties variables = new Properties();

    protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();

    // Every time MyBatis creates a new instance of the resulting object, it uses an ObjectFactory instance to instantiate it.
    // All the default object factory needs to do is instantiate the target class, either through the default no-argument constructor or through the existing parameter mapping to call the constructor with arguments.
    If you want to override the default behavior of an object factory, you can do so by creating your own object factory.
    protected ObjectFactory objectFactory = new DefaultObjectFactory();
    protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

    // Global switch for lazy loading. When enabled, all associated objects are lazily loaded. The fetchType attribute can be set to override the on/off state of an item in a particular association.
    // Default value: false
    protected boolean lazyLoadingEnabled = false;
    protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

    protected String databaseId;
    /**
     * Configuration factory class.
     * Used to create Configuration for loading deserialized unread properties.
     *
     * @see <a href='https://code.google.com/p/mybatis/issues/detail?id=300'>Issue 300 (google code)</a>
     */
    protectedClass<? > configurationFactory;// Language driven registry
    protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();

    // Store all map declarations in Mapper, k-V structure
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
            .conflictMessageProducer((savedValue, targetValue) ->
                    ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
    
    // Data cache, k-V structure
    protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
    
    // Store all result maps from Mapper
    protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
    
    // Store all parameters, from Mapper
    protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
    
    
    protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");

    // Store loaded resource files, such as mapper.xml
    protected final Set<String> loadedResources = new HashSet<>();
    
    // Store SQL fragments
    protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

    // Store incomplete parsed declarations
    protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
    
    // Store cache parses that have not been parsed
    protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
    
    // Store unparsed result mappings
    protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
    
    // Store unparsed methods
    protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();

    / /...
}

Copy the code

Loading process

Mybatis provides many configuration items, for ease of use, provide default values. In most cases, we just need to change as needed. SettingsAsProperties is used to parse the setting configuration:

  private Properties settingsAsProperties(XNode context) {
    // If the node is empty, an empty property object is returned
    if (context == null) {
      return new Properties();
    }
    // Read all setup information and store it in props
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    // Use reflection to check whether the configuration information loaded by props is a known configuration item. If there is an unknown configuration item, an exception is thrown.
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      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;
  }
Copy the code

Reflection is used to check the validity of configuration items in the configuration file. Unknown configuration items may cause exceptions. In addition, this method returns the parsed configuration item, which is not set directly to the configuration, but initialized via the subsequent settingsElement method. The VFS and logImpl configurations need to be loaded.

Type the alias

A type alias sets an abbreviated name for a Java type. It is only used for XML configuration and is intended to reduce redundant fully qualified class name writing. Such as:

    <typeAliases>
        <typeAlias type="com.raysonxin.dataobject.CompanyDO" alias="CompanyDO"/>
    </typeAliases>
Copy the code

Aliases reduce the complexity of using them. They play a big role in type lookup and mapping. TypeAliasesElement is responsible for parsing and loading aliases:

  private void typeAliasesElement(XNode parent) {
    if(parent ! =null) {
      // iterate one by one
      for (XNode child : parent.getChildren()) {
        // Whether to configure aliases as package names
        if ("package".equals(child.getName())) {
          // Get the package name
          String typeAliasPackage = child.getStringAttribute("name");
          // Get all classes under the package by the package name, and then register aliases: if there are annotations, use annotations; Otherwise, use the lowercase first letter of the class name as the alias.
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {// This is how you configure aliases by class names
          // Get the alias
          String alias = child.getStringAttribute("alias");
          // Get type: fully qualified class name
          String type = child.getStringAttribute("type");
          try {
            // Get the type informationClass<? > clazz = Resources.classForName(type);// Alias is empty, default class name starts with lowercase; Not empty, register as alias
            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);
          }
        }
      }
    }
  }
Copy the code

Mybatis supports two types of alias registration:

  • Register by package name: Scans classes against the specified package, using the name specified by the annotation if there is an alias annotation; Otherwise use the class’s first letter lowercase default;
  • Register by class fully qualified name: if an alias is specified, use it as the alias, otherwise use the class’s first lowercase default;

Mybatis has already provided most of the common aliases. In the Configuration constructor, you can check for yourself.

Plugins

MyBatis allows us to intercept calls at certain points in the mapping statement execution process, which allows us to easily extend MyBatis capabilities. By default, MyBatis allows you to intercept method calls using plug-ins:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

The details of the methods in these classes can be discovered by looking at the signature of each method, or by looking directly at the source code in the MyBatis distribution. Let’s take a look at the definition of an interface Interceptor:

public interface Interceptor {

  // Interception interface, which implements interception processing logic
  Object intercept(Invocation invocation) throws Throwable;

  // Add intercepts for the above targets
  Object plugin(Object target);

  // Set plug-in properties
  void setProperties(Properties properties);

}
Copy the code

Custom plug-in

A custom plug-in only needs to implement the Interceptor interface and specify the method signature you want to intercept. The interface method and signature must be exactly the same as defined in Mybatis source code.

@Intercepts( @Signature( type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class} ) )
public class CustomInterceptor implements Interceptor {

    Properties properties = new Properties();

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("before CustomInterceptor");
        Object object = invocation.proceed();
        System.out.println("after CustomInterceptor");
        return object;
    }

    @Override
    public Object plugin(Object target) {
        return target instanceof Executor ? Plugin.wrap(target, this) : target;
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties; }}Copy the code

The CustomInterceptor intercepts the Executor# Query method and outputs some buried information (which can be replaced with its own business logic) before and after the Query. The plugin method implementation logic is that using the plugin.wrap method adds plug-in functionality to the target component when it is Executor. Mybatis -config.xml:

<plugins>
    <plugin interceptor="com.raysonxin.plugin.CustomInterceptor">
        <property name="name" value="abcd"/>
    </plugin>
</plugins>
Copy the code

Take a look at the running effect:

Plug-in loading and execution process

Directly on the code: org. Apache. Ibatis. Builder. XML. XMLConfigBuilder# pluginElement

  // Input parameters are root.evalNode("plugins") nodes
  private void pluginElement(XNode parent) throws Exception {
    // If the node is empty, no processing is done
    if(parent ! =null) {
      // Iterate over the child nodes
      for (XNode child : parent.getChildren()) {
        // Get the plug-in name
        String interceptor = child.getStringAttribute("interceptor");
        // Get the plug-in property list
        Properties properties = child.getChildrenAsProperties();
        // Instantiate the plug-in object
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        // Set the plug-in property field
        interceptorInstance.setProperties(properties);
        // Add the plug-in information to configurtationconfiguration.addInterceptor(interceptorInstance); }}}Copy the code

The plug-in loading process is simple: get the name, get the properties, instantiate the plug-in, set the plug-in properties, and finally add it to Configuration. XMLConfigBuilder saves the plug-in in Configuration#interceptorChain. How does this work in practice? As mentioned above, MyBatis supports plugin enhancements for Executor, StatementHandler, ParameterHandler, ResultSetHandler. Mybatis is in the process of creating the four components in the way of dynamic proxy application plug-in. The following describes how to create the four components in Configuration. NewExecutor is used as an example to describe the application process of the plug-in.

  
  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);
    }
    // Add plugin logic and use dynamic proxy wrapping
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
Copy the code

Component object creation is completed through interceptorChain. PluginAll () method is applied in turn plug-in, four methods are of a similar logic. Interceptor.plugin () is implemented by the plug-in, which enhances it by calling the plugin #wrap method. Take a look at the source logic:

  public Object pluginAll(Object target) {
    // Loop through the plugin in turn
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

  private Plugin(Object target, Interceptor interceptor, Map
       
        , Set
        
         > signatureMap)
        
       > {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

  public static Object wrap(Object target, Interceptor interceptor) {
    // Get the signature of the plug-in class: that is, confirm the method for which the plug-in is enhancedMap<Class<? >, Set<Method>> signatureMap = getSignatureMap(interceptor); Class<? > type = target.getClass();// Get all the interfaces implemented by the plug-in classClass<? >[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {
      // The dynamic proxy creates the proxy class implementation, which in our case is an enhancement for the Executor object.
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
Copy the code

An Executor object wrapped in Plugin#wrap is a proxy object that contains Executor logic. Following the principle of dynamic proxy, let’s look at the execution logic of Plugin#invoke:

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // Query the cached method from the cache
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      // Determine whether the current method is the plug-in enhancement method
      if(methods ! =null && methods.contains(method)) {
        // If yes, the intercept method of the plug-in is executed. The original method is called internally with the entry Invocation#proceed
        return interceptor.intercept(new Invocation(target, method, args));
      }
      // Unenhanced, calls the original method directly
      return method.invoke(target, args);
    } catch (Exception e) {
      throwExceptionUtil.unwrapThrowable(e); }}Copy the code

Note the impact of the plug-in on the original Mybatis to prevent the destruction of the core module of MyBatis. MyBatis provides a powerful mechanism, using the plug-in is very simple, just implement the Interceptor interface, and specify the method signature you want to intercept.

TypeHandler (typeHandler)

When MyBatis sets a parameter in a PreparedStatement or fetches a value from a result set, it uses a type handler to convert the obtained value to a Java type in an appropriate manner. The TypeHandler interface definition also makes it clear what TypeHandler does:

public interface TypeHandler<T> {

  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;

  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  T getResult(CallableStatement cs, int columnIndex) throws SQLException;
}
Copy the code

Custom class processor

If the default type processor does not meet your usage requirements, you can customize the type processor as required. Particular way is: to achieve org. Apache. Ibatis. The TypeHandler interface, or a derived class org. Apache. Ibatis. The BaseTypeHandler, and (optionally) to map it to a JDBC type.

In the second example, CustomTypeHandler is a custom type processor. The Java type of this processor is String and JDBC type is VARCHAR, includeNullJdbcType is true.

@MappedJdbcTypes(value = JdbcType.VARCHAR,includeNullJdbcType = true)
public class CustomTypeHandler extends BaseTypeHandler<String> {
    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, parameter);
    }

    @Override
    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        return rs.getString(columnName);
    }

    @Override
    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        return rs.getString(columnIndex);
    }

    @Override
    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        returncs.getString(columnIndex); }}Copy the code

Then add the following configuration to mybatis-config.xml:

<typeHandlers>
    <typeHandler handler="com.raysonxin.typehandler.CustomTypeHandler"/>
</typeHandlers>
Copy the code

Loading process

The type handler registration process is complicated because the code contains many overloaded methods and looks confusing. Let’s take a look at how TypeHandlerRegistry is designed and look directly at the code.

public final class TypeHandlerRegistry {

  // This field stores the mapping from jdbcType to type handlers, using EnumMap
  private finalMap<JdbcType, TypeHandler<? >> JDBC_TYPE_HANDLER_MAP =new EnumMap<>(JdbcType.class);
  
  // Store the processor mapping for javaType. You need to select the processor based on jdbcType
  private finalMap<Type, Map<JdbcType, TypeHandler<? >>> TYPE_HANDLER_MAP =new ConcurrentHashMap<>();
    
  // The processor type is unknown. The processor type is further resolved internally and routed
  private final TypeHandler<Object> UNKNOWN_TYPE_HANDLER = new UnknownTypeHandler(this);
    
  // Store all type mappings to type handlers, either jdbcType or javaType
  private finalMap<Class<? >, TypeHandler<? >> ALL_TYPE_HANDLERS_MAP =new HashMap<>();

  / / NULL processor
  private static finalMap<JdbcType, TypeHandler<? >> NULL_TYPE_HANDLER_MAP = Collections.emptyMap();// Enumerates type handlers by default
  private Class<? extends TypeHandler> defaultEnumTypeHandler = EnumTypeHandler.class;

  // Common type handlers are registered in the constructor by default
  public TypeHandlerRegistry(a) {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    //....
  }
    
  / /...
}
Copy the code

TypeHandlerRegistry internally maintains relationships between Java types, JDBC types, and type handlers, which are mapped from different perspectives:

  • Handler for JdbcType (JDBC_TYPE_HANDLER_MAP) : this means that when processing a JDBC type, the handler can be obtained from here;
  • JavaType processor (TYPE_HANDLER_MAP) : This field uses two layers of mapping. The first layer is javaType, which represents the list of processors that javaType has; The second layer is the correspondence between the jdbcType and the processor. Taken together, validating a processor requires

    for final validating the processor.
    ,jdbctype>
  • All-type mapping (ALL_TYPE_HANDLERS_MAP) : Maintains all mapers for JDBC and Java types.
  • Null-type handlers and default enumerated type handlers.
  // The input parameter is the root.evalNode("typeHandlers") node
  private void typeHandlerElement(XNode parent) {
    if(parent ! =null) {
      // Traverses the children of the node
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
          // How a single type processor registration is configured
          // Get javaType, which is an alias for the type and should be registered in typeAliasRegistry
          String javaTypeName = child.getStringAttribute("javaType");
          //获取jdbcType
          String jdbcTypeName = child.getStringAttribute("jdbcType");
          // Get the handler name, which is the fully qualified name of the class
          String handlerTypeName = child.getStringAttribute("handler");
          // Get the corresponding type from javaType, which may be null; If specified but not found, an exception is reportedClass<? > javaTypeClass = resolveClass(javaTypeName);// Get JdbcType, enumeration type
          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
          // Get the type of the handlerClass<? > typeHandlerClass = resolveClass(handlerTypeName);if(javaTypeClass ! =null) {
            if (jdbcType == null) {
              // case-1: register by Java type, processor type
              //a, create the handler object typeHandler from the typeHandlerClass;
              // get the jdbcTypes in the typeHandler annotation, which may be empty
              / / c, jdbcTypes! =null: register javaType- jdbctype-handler, includeNullJdbcType=true, register empty types;
              // jdbcTypes==null: only javaType-handler is registered;
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
              // case-2: register according to Java type, JDBC type, processor type
              // create a typeHandler object;
              JavaType - jdbctype-handlertypeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass); }}else {
            // case-3: register by processor type
            //a, get the MappedTypes annotation for typeHandlerClass;
            //b, javaType is not empty: MappedJdbcTypes is obtained internally according to javaType registration.
            // C, this is not the case.
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

  //Case-1 ~ 3 will end up here
  private void register(Type javaType, JdbcType jdbcType, TypeHandler
        handler) {
    if(javaType ! =null) {
      // Query the existing javaType processor from the mapMap<JdbcType, TypeHandler<? >> map = TYPE_HANDLER_MAP.get(javaType);// Does not exist or is empty, perform initialization and add operations
      if (map == null || map == NULL_TYPE_HANDLER_MAP) {
        map = new HashMap<>();
        TYPE_HANDLER_MAP.put(javaType, map);
      }
      / / associated javaType - jdbcType - handler
      map.put(jdbcType, handler);
    }
    // Add the total mapping
    ALL_TYPE_HANDLERS_MAP.put(handler.getClass(), handler);
  }
Copy the code

As you can see from the code, JDBC_TYPE_HANDLER_MAP is just an addition to the constructor logic, which is the default handler for Mybatis. Our custom handlers will be added to the TYPE_HANDLER_MAP and ALL_TYPE_HANDLERS_MAP, although there are different registration methods, but the same result is achieved through the above method. To sum up:

  • Type handlers that are customized through configuration files are added to TYPE_HANDLER_MAP, ALL_TYPE_HANDLERS_MAP, and fetch type handlers are queried using javaType in preference. If a custom type handler defines javaType or jdbcType repeatedly, the system default handler will be overridden.
  • If javaType is declared in both the annotation and XML configuration of the type processor, the javaType configured in the XML takes effect.

Mapper

The real power of MyBatis lies in its statement mapping, which is its magic. Because of its exceptional power, the MAPper’s XML file is relatively simple. If you compare it to JDBC code with the same functionality, you’ll immediately see that nearly 95% of the code is saved. MyBatis aims to reduce usage costs and allow users to focus more on SQL code. Mapper is a powerful tool for interface programming in Mybatis. It uses dynamic proxy technology to realize the dynamic binding between the interface and THE SQL Statement in MAPper, and avoids the complicated process of manually implementing the Statement in JDBC. Mybatis provides several ways to support mapper resource lookup: you can use resource references relative to the classpath, or fully qualified resource locator (including file:/// URL), or class and package names, etc.

Mapper element

The SQL mapping file has only a few top-level elements (listed in the order they should be defined) :

  • Cache – The cache configuration for this namespace.
  • Cache-ref – References the cache configuration of other namespaces.
  • ResultMap – describes how to load objects from a database result set, and is the most complex and powerful element.
  • ParameterMap – Old-fashioned style parameter mapping. This element is deprecated and may be removed in the future! Use inline parameter mapping. This element is not covered in the documentation.
  • SQL – reusable block of statements that can be referenced by other statements.
  • Insert – Mapping insert statements.
  • Update – Mapping update statement.
  • Delete – Mapping delete statement.
  • Select – Mapping query statement

The above top-level elements also have more attributes that control their behavior. You can refer to the official documentation, which is no longer posted here.

Loading process

Let’s take a look at how Mybatis loads and parses Mapper with examples and source code, and then take another look at how it’s executed. Example configuration information, in which companymapper. XML is defined, imported into mybatis-config.xml as a resource.

<! --mybatis-config.xml-->
<mappers>
    <mapper resource="mapper/CompanyMapper.xml"/>
</mappers>

<! --CompanyMapper.xml-->
<mapper namespace="com.raysonxin.dao.CompanyDao">

    <resultMap id="baseResultMap" type="com.raysonxin.dataobject.CompanyDO">
        <id column="id" property="id"/>
        <result column="name" property="name"/>
        <result column="cpy_type" property="cpyType"/>
    </resultMap>

    <sql id="BaseColumns">
        id,name,cpy_type
    </sql>

    <select id="selectById" resultMap="baseResultMap">
        select
        <include refid="BaseColumns"></include>
        from company
        where id= #{id} and name = #{name}
    </select>
</mapper>
Copy the code

The mapper parse entry is XMLConfigBuilder#mapperElement, and the code comments are used to walk down the process.

  // The entry parameter is root.evalNode("mappers"), which is the mappers node in the example, which can contain multiple Mapper child nodes
  private void mapperElement(XNode parent) throws Exception {
    if(parent ! =null) {
      for (XNode child : parent.getChildren()) {
        // Add mapper in package mode
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          // There are three methods: resource import, resource fully qualified path, class name, and mapper node attribute values respectively
          // Only one of the three values can be configured, otherwise an exception will be reported (the last else)
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          
          // Resource mode
          if(resource ! =null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // Use the XMLMapperBuilder class for mapper parsing, which is the focus.
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } 
          // Resource fully qualified path mode
          else if (resource == null&& url ! =null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            // Use the XMLMapperBuilder class for mapper parsing, as above.
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } 
          // Class name
          else if (resource == null && url == null&& mapperClass ! =null) { Class<? > mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); }// None of the above three cases, abnormal
          else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
Copy the code

The sample code introduces Mapper as a Resource, follows the line, creates the XMLMapperBuilder and calls its parse method. Similar to XMLConfigBuilder, XPathParser is used for XML parsing and MapperBuilderAssistant is used to build MappedStatement.

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }
Copy the code

Next comes the parsing process, the XMLMapperBuilder#parse method. The general process is as follows: If the resource has not been loaded, parse the elements in the mapper file, add the Mapper to the loaded resource, and bind the Mapper and namespace. ResultMap, CacheRef, and Statement that have not been completed are processed regardless of whether the current resource has been loaded.

  public void parse(a) {
    // Check whether the resource is loaded
    if(! configuration.isResourceLoaded(resource)) {// This is the core method of mapper parsing. The input parameter is the resource file corresponding to mapper, as shown in the companymapper.xml example
      configurationElement(parser.evalNode("/mapper"));
      // Add loaded resources to Configuration
      configuration.addLoadedResource(resource);
      // This method binds mapper to namespace
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
Copy the code

Core analytic way is configurationElement, which in turn in analytic mapper file cache – ref, cache, parameterMap, resultMap, SQL, select | insert | update | delete, This time focuses on the analysis of the following three processes.

  private void configurationElement(XNode context) {
    try {
      / / retrieve the namespace in the mapper, example: com.raysonxin.dao.Com panyDao
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      // Set the namespace of builderAssistant
      builderAssistant.setCurrentNamespace(namespace);
      // Parse the cache-ref contents
      cacheRefElement(context.evalNode("cache-ref"));
      // Parse the cache contents
      cacheElement(context.evalNode("cache"));
      // Parse the parameterMap node
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // Parse the resultMap node
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      // Parse the SQL node
      sqlElement(context.evalNodes("/mapper/sql"));
      / / build the statement
      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); }}Copy the code

resultMap

You can define multiple ResultMaps in the following table. Usually, a commonly used return field is defined as a resultMap for reference in query statements. Start with resultMapElements.

  private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried}}}private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList(), null);
  }

  // The real parsing begins here
  private ResultMap resultMapElement(XNode resultMapNode, List
       
         additionalResultMappings, Class
         enclosingType)
        throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    // Get the ID attribute of the resultMap, in the example baseResultMap
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    // Get the resultMap type attribute, followed by the default setting order
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
    // Gets the extends property
    String extend = resultMapNode.getStringAttribute("extends");
    // Get the autoMapping attribute
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
    // Get the type object corresponding to type, first from typeAliasRegistry, otherwise created according to full-path qualified name reflectionClass<? > typeClass = resolveClass(type);if (typeClass == null) {
      typeClass = inheritEnclosingType(resultMapNode, enclosingType);
    }
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    // Parse the child nodes in turn
    for (XNode resultChild : resultChildren) {
      // whether it is a constructor
      if ("constructor".equals(resultChild.getName())) {
        // Handle the constructor
        processConstructorElement(resultChild, typeClass, resultMappings);
      } 
      / / discriminator discriminator
      else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } 
      // Other nodes
      else {
        List<ResultFlag> flags = new ArrayList<>();
        // Whether it is an ID node
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        / / build resultMap: buildResultMappingFromContext
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throwe; }}Copy the code

Regardless of the complex resultMap, node type for the id and the result of our sample two kinds, we directly into method buildResultMappingFromContext:

  private ResultMapping buildResultMappingFromContext(XNode context, Class
        resultType, List
       
         flags)
        throws Exception {
    String property;
    if (flags.contains(ResultFlag.CONSTRUCTOR)) {
      property = context.getStringAttribute("name");
    } else {
      // Get the property value corresponding to the javaType field
      property = context.getStringAttribute("property");
    }
    // Table fields
    String column = context.getStringAttribute("column");
    JavaType / /
    String javaType = context.getStringAttribute("javaType");
    //获取jdbcType
    String jdbcType = context.getStringAttribute("jdbcType");
    String nestedSelect = context.getStringAttribute("select");
    String nestedResultMap = context.getStringAttribute("resultMap",
        processNestedResultMappings(context, Collections.<ResultMapping> emptyList(), resultType));
    String notNullColumn = context.getStringAttribute("notNullColumn");
    String columnPrefix = context.getStringAttribute("columnPrefix");
    // Type handler
    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"));
    // Get javaType objectsClass<? > javaTypeClass = resolveClass(javaType);// Get the type handlerClass<? extends TypeHandler<? >> typeHandlerClass = resolveClass(typeHandler);// JDBC type enumeration
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    // Create a ResultMapping object and return it
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }
Copy the code

This parsing process includes the logic for high-level uses of resultMap, such as Association, collections, and so on, so we’ll start with the simple, common approach. BuildResultMappingFromContext eventually returned to the resultMap a field mapping information, build the Java type mapping relationship with database field. In this way, all fields in the resultMap are mapped by traversing all fields.

So we go back to the resultMapElement method, walk through all the nodes and get the resultMappings, and create a ResultMapResolver object. As you can tell from the name, this class is the ResultMap parser, which ultimately translates the ResultMap configuration information into the MyBaits ResultMap object and adds it to the Configuration.

sql

In practical development, we can use SQL tags to define those reusable SQL fragments to simplify the configuration of mapper files. When mybatis parse SQL, it is only an intermediate process, it is parsing the select | update | insert | the foundation of the delete statement.

  private void sqlElement(List<XNode> list) {
    if(configuration.getDatabaseId() ! =null) {
      sqlElement(list, configuration.getDatabaseId());
    }
    sqlElement(list, null);
  }

  private void sqlElement(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      // Add a namespace. The result is namespace+"."+ ID
      id = builderAssistant.applyCurrentNamespace(id, false);
      // Check whether datbaseId requirements are met
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        //sqlFragments hold all SQL fragmentssqlFragments.put(id, context); }}}Copy the code

select|insert|update|delete

Select | insert | update | the delete command for SQL statements in the same name, is the most frequent part, we use mybatis will eventually put these labels into MappedStatement, stored in the configuration, It is also bound to Mapper interface methods at run time.

  private void buildStatementFromContext(List<XNode> list) {
    if(configuration.getDatabaseId() ! =null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        statementParser.parseStatementNode();
      } catch(IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); }}}Copy the code

From the code you can see the XMLStatementBuilder, which is the processor for the command tag, and the core method is parseStatementNode. So far, we’ve seen XMLConfigBuilder, XMLMapperBuilder. Take a look at the process:

  public void parseStatementNode(a) {
    // Get the ID attribute of the tag, such as selectById
    String id = context.getStringAttribute("id");
    / / databaseId, is empty
    String databaseId = context.getStringAttribute("databaseId");

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

    FetchSize / / attributes
    Integer fetchSize = context.getIntAttribute("fetchSize");
    / / attribute a timeout
    Integer timeout = context.getIntAttribute("timeout");
    ParameterMap / / attributes
    String parameterMap = context.getStringAttribute("parameterMap");
    ParameterType / / attributes
    String parameterType = context.getStringAttribute("parameterType");
    // Parse the parameter type informationClass<? > parameterTypeClass = resolveClass(parameterType);// Get the attribute resultMap
    String resultMap = context.getStringAttribute("resultMap");
    // Get the attribute resultType
    String resultType = context.getStringAttribute("resultType");
    String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<? > resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType");
    // Get the attribute statementType. If not set, use PREPARED
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    // Get nodeName, which is the type of tag, select, INSERT, etc
    String nodeName = context.getNode().getNodeName();
    // Command type
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // Check whether this is the select command
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // Whether to refresh the cache
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // Whether to use cache
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered".false);

    // Include Fragments before parsing
    // This parses include tags in SQL statements
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // Parse selectKey after includes and remove them.
    // Parse the selectKey tag
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    // Create a SqlSource, which must be executed after the inclue and selectKey are parsed.
    // Different types of SQLSources are created internally according to whether the SQL statement contains dynamic labels, and parameter mapping information required by the SQL is parsed
    // This place needs a separate article for analysis
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    // Process the primary key generator
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // Create MappedStatement and add it to configuration
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
Copy the code

Source code process comb:

  • First parse the basic attributes of the tag, such as ID and fetchSize,
  • Parse the inclue tag in the SQL statement (replace and concatenate the contents of include),
  • Handle the selcetKey tag (required for a specific databaseProvider),
  • This is the core content of Mybatis, which involves parameter extraction and dynamic assignment in SQL statements, etc., and will be explained in detail later.
  • Processing KeyGenerator;
  • Use builderAssistant to create the MappedStatement and add it to the Configuration

conclusion

Configuration is the global Configuration class of MyBatis. Everything required by myBatis runtime is cached here, which accompanies the entire life cycle of MyBatis. In theory, Configuration is a code mapping of the Configuration file mybatis-config. XML. Mybatis provides sufficient flexibility and extensibility to facilitate developers to change their running behavior through the Configuration file.

In this paper, through examples and source code, the parsing process of Mybatis -config. XML and most of the content of Configuration are combed out. Due to the priority of level, many contents in the article are not clearly described, and I will improve them as I continue to learn.

Public search “code road mark”, point of concern not lost!