Initialization of MyBatis source code analysis

Keywords: Programming Mybatis xml Database JDBC

Mybatis source code analysis I (initialization process)

Recently, I learned the source part of mybatis. Now I am going to record my understanding process while learning. This article mainly records the initial process of mybatis, mainly including the following points:

1. What did initialization do

2. How to parse the parameters of mybatis-config.xml

3. Design mode involved

1. Initialization

First let's look at a simple example of using Mybatis.

Suppose we have a user table as follows:

create table user(
    id int primary key auto_increment,
   	name char(10) not null,
    password char(20) not null,
    age int not null,
);

Create an entity class User as follows:

package com.alibaba.entity;

public class User{
    private Integer id;
    private String name;
    private String password;
    private Integer age;
    // Omit getter and setter
}

Declare an interface as follows:

package com.alibaba.dao;
import com.alibaba.entity.User;
public interface UserDao{
    int insert(User user);
}

Write another mapper.xml file corresponding to the interface

<?xml version="1.0" encoding="UTF-8" ?>   
<!DOCTYPE mapper   
PUBLIC "-//ibatis.apache.org//DTD Mapper 3.0//EN"  
"http://ibatis.apache.org/dtd/ibatis-3-mapper.dtd"> 
<mapper namespace="com.alibaba.dao.UserDao">
   <insert id="insert" ParameterType="com.alibaba.entity.User" > 
      insert into user(name,password,age) values(#{name},#{password},#{age})
   </insert>
</mapper>

Finally, write mybatis-config.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>

  <!-- Appoint properties Configuration file, I configure database related -->
  <properties resource="dbConfig.properties"></properties>
  
  <!-- Appoint Mybatis Use log4j -->
  <settings>
     <setting name="logImpl" value="LOG4J"/>
  </settings>
      
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
          <!--
          If the database configuration is not specified above properties File, you can configure it directly here 
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test1"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
         -->
         
         <!-- The above specifies the database configuration file, which also contains the corresponding four properties -->
         <property name="driver" value="${driver}"/>
         <property name="url" value="${url}"/>
         <property name="username" value="${username}"/>
         <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  
  <!-- Mapping files, mybatis The essence will be explained later -->
  <mappers>
    <mapper resource="com/alibaba/dao/user-mapper.xml"/>
  </mappers>
</configuration>

Next write the test class as follows:

package com.alibaba.test;
// Ellipsis import
public class TestMain(){
    public static void main(String [] args){
        String resource = "mybatis-config.xml";
        SqlSessionFactory sqlSessionFactory = null;
        
        try{
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourcesAsReader(resource));
        }catch(Exception e){
            e.printStackTrace();
        }
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        User user = new User();
        user.setName("double");
        user.setPassword("double");
        user.setAge(25);
        userDao.insert(user);
    }
}

The above is a simple example of using mybatis. The above code goes through mybatis initialization - > Creating sqlSession - > getting mapper object with sqlSession - > executing statement with mapper object.

What elements are contained in the mybatis-config.xml file (see the review of mybatis-config.xml above):

​ Configuration

-- property value configuration

-- settings settings

-- typeAliases type alias configuration

-- typeHandlers

-- objectFactory

-- plugins plug-in configuration

-- information configuration such as environments data source

-- mapper mapper configuration

Finally, Mybatis will assemble all the information in the Configuration into a Configuration object for the whole framework to use. It can be said that initialization is to construct the Configuration object.

2. How to parse the mybatis-config.xml configuration file?

First, recall the main statement in the above example:

​ new SqlSessionFactoryBuilder().build(Resources.getResourcesAsReader(resource));

First, describe the steps of mybatis initialization in language:

1. Call the build() method of SqlSessionFactoryBuilder and pass in the configuration file

2. Sqlsessionfactorybuilder will submit the incoming configuration information to the XMLConfigBuilder object for processing

3. Call the parse() method of XMLConfigBuilder to parse the data of each node in the configuration file

4. Assemble the result of analysis into a Configuration object

5. SqlSessionFactoryBuilder creates DefaultSqlSessionFactory and passes Configuration to its constructor

6. SqlSessionFactoryBuilder returns DefaultSqlSessionFactory for the user to use

The figure is as follows:

Let's look at the specific code analysis:

// Next, extract the source code of the method used in the sample code
public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
}

public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      // The configuration data source is handed over to this object for processing. The detailed processing steps are ignored, which is temporarily understood as the configuration file
      // Each Node configuration in is encapsulated as a Node object
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      // After encapsulating the configuration file in nodes, call the parse() method to start parsing the specific content of each node
      // The parsed content is assembled into a Configuration object and passed into the build method to build a
      // DefaultSqlSessionFaction
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
}
// Finally, call this method to return DefaultSqlSessionFactory for user use.
public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
}

So what does the parse() method do? Start analyzing this method

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // Call this method to start parsing under the root node configuration of the configuration file
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}

// This method is about the logic of parsing all the information in the configuration file one by one. Here are some common parsing points
private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

Properties element of common configuration resolution (root. Evalnode ("properties"))

// 1,<properties resource="dbConfig.properties"></properties>
// 2,<properties>
//		<property name="" value=""/>
//	</properties>
// 3,<properties url="xxxxx"></properties>
// This is a way to parse some attribute values put in configuration. There are three ways to write configuration, as shown above
// Through the method parameters, you can see that the configuration information is encapsulated into XNode objects
private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
    // First read the default configuration in the format of method 2
    Properties defaults = context.getChildrenAsProperties();
    // Get the value of resource in < Properties >
    String resource = context.getStringAttribute("resource");
    // Get the value of url in < Properties >
    String url = context.getStringAttribute("url");
    // Cannot configure resource and url for < Properties > at the same time, otherwise an exception will be thrown
    if (resource != null && url != null) {
      throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
    }
    // If the resource is not empty, read in the configuration in the resource specified file. In this step, the external configuration file will overwrite mybatis
    // Properties configured in the configuration file
    if (resource != null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if (url != null) {
      defaults.putAll(Resources.getUrlAsProperties(url));
    }
    // Get the existing property value of the configuration object
    Properties vars = configuration.getVariables();
    if (vars != null) {
      // If the existing property is not empty, merge it with the property value read now
      defaults.putAll(vars);
    }
    // Finally, update the property values of the parser and configuration objects to the latest values
    parser.setVariables(defaults);
    configuration.setVariables(defaults);
  }
}

The rest of the configuration resolution is similar. There are not too many records here.

3. Design pattern used

3.1 construction mode

Application 1:

​ new SqlSessionFactoryBuilder.bulid(InputStream);

You can see that the first application is to construct SqlSessionFactory. Mybatis uses the constructor pattern

Application 2:

Builder pattern used when creating Environment objects

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          // Use builder mode here to create Environment objects
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
 }

Specific Environment internal code is a standard builder pattern

public final class Environment {
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;

  public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
    if (id == null) {
      throw new IllegalArgumentException("Parameter 'id' must not be null");
    }
    if (transactionFactory == null) {
      throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
    }
    this.id = id;
    if (dataSource == null) {
      throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
    }
    this.transactionFactory = transactionFactory;
    this.dataSource = dataSource;
  }

  public static class Builder {
    private String id;
    private TransactionFactory transactionFactory;
    private DataSource dataSource;

    public Builder(String id) {
      this.id = id;
    }

    public Builder transactionFactory(TransactionFactory transactionFactory) {
      this.transactionFactory = transactionFactory;
      return this;
    }

    public Builder dataSource(DataSource dataSource) {
      this.dataSource = dataSource;
      return this;
    }

    public String id() {
      return this.id;
    }

    public Environment build() {
      return new Environment(this.id, this.transactionFactory, this.dataSource);
    }

  }

  public String getId() {
    return this.id;
  }

  public TransactionFactory getTransactionFactory() {
    return this.transactionFactory;
  }

  public DataSource getDataSource() {
    return this.dataSource;
  }

}

The above is a simple record of the initialization process, and then more core technologies of Mybatis source code will be recorded from shallow to deep.

Posted by cosmo7 on Sat, 07 Mar 2020 19:37:44 -0800