The sharp blade comes out of the scabbard and Mybatis is initialized

Keywords: Java Database Spring

1, Foreword

For any framework, a series of initialization is required before use, and MyBatis is no exception.

2, What did the initialization of MyBatis do

2.1 the initialization process of mybatis is the process of loading the configuration information required by its own runtime

The initialization of any framework is nothing more than loading the configuration information required by its own runtime. So is Mybatis. The initialization process of Mybatis is the process of loading the configuration information required by its own runtime.

Spring's initialization process is the process of loading the configuration information required by its own runtime, and Mybatis's initialization process is the process of loading the configuration information required by its own runtime.

2.2 what are the configuration information of mybatis

The configuration information of MyBatis roughly includes the following information, and its hierarchical structure is as follows:

× configuration to configure

   × properties attribute
   × settings set up
   × typeAliases Type naming
   × typeHandlers Type processor
   × objectFactory Object factory
   × plugins plug-in unit
   × environments environment
       × environment environment variable
       × transactionManager Transaction manager
       × dataSource data source
× Mapper

The above configuration information of MyBatis will be configured in the XML configuration file. Then, these information will be loaded into MyBatis. How is MyBatis maintained?

2.3 mybatis-config.xml and Configuration classes

MyBatis adopts a very straightforward and simple way: the org.apache.ibatis.session.Configuration object is used as a container for all Configuration information. The organizational structure of the Configuration object is almost the same as that of the XML Configuration file (of course, the function of the Configuration object is not limited to this. It is also responsible for creating some objects used internally by MyBatis, such as Executor, etc.). As shown in the following figure:

After MyBatis initializes the Configuration information, users can use MyBatis for database operations. In other words, the process of MyBatis initialization is the process of creating Configuration objects.

2.4 two methods of mybatis initialization

There are two ways to initialize mybatis (corresponding to mybatis boot layer: Based on XML configuration file + based on Java API):

(1) XML based Configuration file: the XML based Configuration file method is to put all the Configuration information of MyBatis in the XML file. MyBatis assembles the Configuration file information into an internal Configuration object by loading and using the XML Configuration file.

(2) Based on Java API: this method does not use XML Configuration files. MyBatis users need to manually create a Configuration object in Java code, and then manually set the Configuration parameter into the Configuration object.

Next, through MyBatis initialization based on XML Configuration file, we will deeply explore how MyBatis constructs Configuration object through Configuration file and uses it.

3, MyBatis the process of creating Configuration objects based on XML Configuration files

3.1 locate the key sentence of Mybatis initialization

Now let's start with a simple example of using MyBatis to deeply analyze how MyBatis completes initialization and what it initializes. See the following code:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 1. Mybatis initialization (sqlsessionfactory obtained from InputStream)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2. Create SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3. Execute SQL statement and return results
List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");

Readers with mybatis experience will know that the above statement is used to execute the SQL statement defined by com.foo.bean.BlogMapper.queryAllBlogInfo and return a List result set. In general, the above code goes through three processes: "mybatis initialization -- > create sqlsession -- > execute SQL statement and return results".

The function of the above code is to create SqlSessionFactory objects according to the configuration file mybatis-config.xml, then generate SqlSession and execute SQL statements. The initialization of mybatis occurs in the third sentence, which is this sentence:

SqlSessionFactory sqlSessionFactory =
    new SqlSessionFactoryBuilder().build(inputStream);

Now let's see what happened in the third sentence.

3.2 basic process of mybatis initialization

SqlSessionFactoryBuilder generates a Configuration object based on the incoming data stream (inputStream above), and then creates a default SqlSessionFactory instance based on the Configuration object.

3.2.1 Mybatis initialization sequence diagram

The basic initialization process is shown in the following sequence diagram:

The classes and interfaces involved in the sequence diagram: SqlSessionFactoryBuilder, XMLConfigBuilder, Configuration, XPathParser, xmlmapterentityresolver

(1) SqlSessionFactoryBuilder: it is a non abstract class. The constructor of SqlSessionFactory is used to create SqlSessionFactory and adopts the Builder design pattern;
(2) XMLConfigBuilder: use inputStream/reader to get the XMLConfigBuilder class object, and the reference name is parser, which is used to parse() to get the Configuration object.

The explanation corresponding to the above sequence diagram is:

// In the first step of mybatis initialization, call build(), 1 in the figure above
build(inputStream) call build(inputStream,null,null) 
// The second step of mybatis initialization is to manually create XMLConfigBuilder, as shown in Figure 2 above 
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null,null);
// In the third step of mybatis initialization, the parse() method constructs the configuration object, as shown in 3 and 4 in the figure above
Configuration configuration=parser.parse();   
// In step 4 of mybatis initialization, create SqlSessionFactory using the Configuration object, as shown in Figure 5 above
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

3.2.2 four steps of mybatis initialization

As shown in the figure above, mybatis initialization requires the following simple steps:

  1. The outermost layer calls the build(inputStream) method of the SqlSessionFactoryBuilder object. The internal details of this method are 2, 3 and 4 below;

  2. new XMLConfigBuilder() bottom layer: SqlSessionFactoryBuilder will create an XMLConfigBuilder object according to the input stream inputStream and other information;

  3. Parse() the underlying parse() gets the Configuration object: SqlSessionFactoryBuilder calls the parse() method of XMLConfigBuilder object, and the parse() method of XMLConfigBuilder object returns the Configuration object;

  4. bulid() gets the DefaultSessionFactory object from the underlying Configuration object: SqlSessionFactory builder creates a DefaultSessionFactory object according to the Configuration object, which is the implementation class of the SqlSessionFactory interface. The return type returned by all build() is SqlSessionFactory; SqlSessionFactory builder returns the DefaultSessionFactory object to the Client for use by the Client.

3.2.3 Mybatis initialization four step code analysis

The code related to SqlSessionFactoryBuilder is as follows:

SqlSessionFactoryBuilder.java

public SqlSessionFactory build(InputStream inputStream)
{   //1. Call the build(inputStream) method of SqlSessionFactoryBuilder object
    return build(inputStream, null, null);
}
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties)
{
    try
    {
        //2. Create XMLConfigBuilder object to parse XML Configuration file and generate Configuration object
        XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        //3. Parse the information in the XML Configuration file into a Java object Configuration object
        Configuration config = parser.parse();
        //4. Create a SqlSessionFactory object based on the Configuration object
        return build(config);
    }
    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.
        }
    }
}
//From here, we can see that MyBatis creates SqlSessionFactory internally through the Configuration object. Users can also construct the Configuration object through the API and call this method to create SqlSessionFactory
public SqlSessionFactory build(Configuration config)
{
    return new DefaultSqlSessionFactory(config);
}

3.2.4 interfaces and classes involved in mybatis initialization

The above initialization process involves the following objects:

(1) SqlSessionFactoryBuilder: it is a non abstract class. It is the constructor of SqlSessionFactory. It is used to create SqlSessionFactory and adopts the Builder design pattern

(2) XMLConfigBuilder: responsible for parsing the mybatis-config.xml Configuration file into a Configuration object for use by sqlsessionfactorybuilder to create SqlSessionFactory

(3) Configuration: a non abstract class that can instantiate an object. This object is all the mybatis configuration information in the mybatis-config.xml file

(4) SqlSessionFactory: the SqlSession Factory class is an interface, and the implementation class is DefaultSqlSessionFactory. SqlSession objects are created in the form of factories, using the Factory factory design pattern

3.3 process of creating Configuration object in parse()

Problem: in the basic process of MyBatis initialization, when SqlSessionFactoryBuilder executes the build() method, it calls the parse() method of XMLConfigBuilder, and then returns the Configuration object. So how does the parse() method process XML files and generate Configuration objects?

Answer: four steps, as follows:
The first step is from XMLConfigBuilder to XPathParser
Step 2: parse the configuration node
Step 3: set the parsed value to the Configuration object
Step 4: return the Configuration object

3.3.1 from XMLConfigBuilder to XPathParser

The XMLConfigBuilder class converts the information of the XML configuration file into a Document object, and the XML configuration definition file DTD into an xmlmapterentityresolver object, and then encapsulates the two (Document object and xmlmapterentityresolver object) into an XpathParser object. The function of the XpathParser is to provide the operation of obtaining basic DOM Node information according to the Xpath expression. As shown in the figure below:

The information of the XML configuration file (mybatis-config.xml + XxxMapper.xml) is converted into a Document object, while the XML configuration definition file DTD (mybatis-3-config.dtd + mybatis-3-mapper.dtd, where 3 represents the version of mybatis) is converted into an xmlmapterentityresolver object, and then the two are encapsulated into an XpathParser object.

The function of XpathParser is to obtain basic DOM Node information according to the Xpath expression, as shown below:

3.3.2 parsing the configuration node

XMLConfigBuilder calls the parse() method: it will take out the Node object corresponding to the < configuration > Node from the XPathParser, and then parse the child nodes of this Node: properties, settings, typeAliases,typeHandlers, objectFactory, objectWrapperFactory, plugins, environments,databaseIdProvider, mappers, one of 10. The code is as follows:

    public Configuration parse()
    {
        if (parsed)
        {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        //There is no such sentence in the source code, only parseConfiguration(parser.evalNode("/configuration"));
        //In order to make readers see more clearly, the source code is divided into the following two sentences
        XNode configurationNode = parser.evalNode("/configuration");
        parseConfiguration(configurationNode);
        return configuration;
    }
    /*
    Resolve the child node information under the "/ Configuration" node, and then set the resolution result to the Configuration object
    */
  private void parseConfiguration(XNode root) {
    try {
      //1. First process the properties node	
      propertiesElement(root.evalNode("properties")); 
      //issue #117 read properties first
      //2. Handle typeAliases
      typeAliasesElement(root.evalNode("typeAliases"));
      //3. Processing plug-ins
      pluginElement(root.evalNode("plugins"));
      //4. Process objectFactory
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.objectWrapperFactory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.settings
      settingsElement(root.evalNode("settings"));
      //7. Dealing with environments
      environmentsElement(root.evalNode("environments")); 
      // read it after objectFactory and objectWrapperFactory issue #631
      //8.database
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9. typeHandlers
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10 mappers
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

Note: in the above code, another very important thing is the mapperElements(root.evalNode("mappers") method for parsing the sub node < mappers > of the XML configuration file, which will parse the Mapper.xml configuration file we configured. The Mapper configuration file can be said to be the core of MyBatis, and the characteristics and concepts of MyBatis are reflected in the configuration and design of Mapper.

3.3.3 set the parsed value to the Configuration object

The process of resolving child nodes will not be introduced here one by one. Users can carefully figure it out by referring to the MyBatis source code. Let's look at the above environmentsElement(root.evalNode("environments"); How to parse the information of environments and set it in the Configuration object:

/*
   Resolve the environments node and set the result to the Configuration object
   Note: when creating the environment, if SqlSessionFactoryBuilder specifies a specific environment (i.e. data source);
         The Environment object of the specified Environment (data source) is returned; otherwise, the default Environment object is returned;
         In this way, MyBatis can connect multiple data sources
*/
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))
            {
                //1. Create a transaction factory, TransactionFactory, and continue to drill down into child nodes
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                //2. Create data source
                DataSource dataSource = dsFactory.getDataSource();
                //3. Construct Environment object
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                .transactionFactory(txFactory)
                .dataSource(dataSource);
                //4. Set the created environment object into the configuration object
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}
private boolean isSpecifiedEnvironment(String id)
{
    if (environment == null)
    {
        throw new BuilderException("No environment specified.");
    }
    else if (id == null)
    {
        throw new BuilderException("Environment requires an id attribute.");
    }
    else if (environment.equals(id))
    {
        return true;
    }
    return false;
}

3.3.4 return Configuration object

public Configuration parse()
{
    if (parsed)
    {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //There is no such sentence in the source code, only parseConfiguration(parser.evalNode("/configuration"));
    //In order to make readers see more clearly, the source code is divided into the following two sentences
    XNode configurationNode = parser.evalNode("/configuration");
    parseConfiguration(configurationNode);
    return configuration;   // After completion, return the configuration object to the programmer
}

3.4 sequence diagram refinement of the basic process of mybatis initialization

3.4.1 sequence diagram component of mybatis initialization basic process

We refine the sequence diagram of the above basic process of MyBatis initialization, including the process of parse() method parsing mybatis-config.xml into Configuration object, as shown in the following figure:

The classes and interfaces involved in the sequence diagram: SqlSessionFactoryBuilder, XMLConfigBuilder, Configuration, XPathParser, xmlmapterentityresolver

(1) SqlSessionFactoryBuilder: it is a non abstract class. It is the constructor of SqlSessionFactory. It is used to create SqlSessionFactory and adopts the Builder design pattern

(2) XMLConfigBuilder: use inputStream/reader to get the class object of XMLConfigBuilder. The reference name is parser. It is used to parse() to get the Configuration object. It is responsible for parsing the mybatis-config.xml Configuration file into a Configuration object for sqlsessionfactorybuilder to use and create SqlSessionFactory

(3) Configuration: a non abstract class that can instantiate an object. This object is all the mybatis configuration information in the mybatis-config.xml file

(4) SqlSessionFactory: the SqlSession Factory class is an interface, and the implementation class is DefaultSqlSessionFactory. SqlSession objects are created in the form of factories, using the Factory factory design pattern

(5) Document: it is converted from the information of the XML configuration file and finally encapsulated in the XpathParser object.

(6) Xmlmapterentityresolver: it is converted from the XML configuration definition file DTD and finally encapsulated in the XpathParser object.

(7) XPathParser: XMLConfigBuilder converts the information of the XML configuration file into a Document object, and the XML configuration definition file DTD into an xmlmapterentityresolver object, and then encapsulates them into an XpathParser object.

Step 1 of parse() method: XMLConfigBuilder will convert the information of XML configuration file into Document object and XML configuration definition file DTD into xmlmapterentityresolver object, and then encapsulate them into XpathParser object. XpathParser is used to obtain basic DOM Node information according to Xpath expression

3.4.2 sequence diagram of basic process of mybatis initialization

The sequence diagram of the basic process of MyBatis initialization is explained as follows:

The first step of mybatis initialization is to call build(), which is the general name of the second, third and fourth steps

The second step of mybatis initialization is to manually create XMLConfigBuilder
1 xmlconfigbuilder parser in the above figure = new xmlconfigbuilder (InputStream, null, null);
2.1 2.2 just create an xmlmapterentityresolver object;
3.1.3.2 create an XPathParser object;
4.1 4.2 create a new Configuration object;

In the third step of mybatis initialization, the parse() method constructs the configuration object
The third step of mybatis initialization is the explanation from 5 to 11 in the above figure,
5 is to call the parse() method
6.1, 7.1, 7.2 and 6.2 are used to parse the configuration node in mybatis-config.xml
Then, the loop of the child nodes in the child configuration node. 8.1, 8.2 is to parse the child nodes, 9 is to obtain the corresponding values, and 10 is to set the values into the configuration object
11 is to return the configuration object Configuration configuration=parse();
parse() step 1: from XMLConfigBuilder to XPathParser
parse() step 2: parse the configuration node
parse() step 3: set the parsed value to the Configuration object
parse() step 4: return the configuration object

In the fourth step of mybatis initialization, after obtaining the Configuration object, use the Configuration object to create SqlSessionFactory, that is, in the figure

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

Note: in the above figure, 4.1 and 4.2 only return an empty configuration object, and 5-11 return a constructed configuration class object, which is different.

3.5 manually load XML Configuration file, create Configuration object and complete initialization

We can use XMLConfigBuilder to manually parse XML Configuration files to create Configuration objects, and create and use SqlSessionFactory objects. The code is as follows:

String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//Manually create XMLConfigBuilder and parse the Configuration object
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null,null);
Configuration configuration=parse();
//Create SqlSessionFactory using Configuration object
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
//Using MyBatis
SqlSession sqlSession = sqlSessionFactory.openSession();
List list = sqlSession.selectList("com.foo.bean.BlogMapper.queryAllBlogInfo");

4, Design patterns involved in Mybatis initialization

The initialization process involves creating various objects, so some creative design patterns will be used. In the process of initialization, the Builder mode is used more, which involves the use of at least two Builder modes: the creation of SqlSessionFactory and the creation of database connection Environment object.

4.1 creation of sqlsessionfactory

When creating SqlSessionFactory, different parameters will be provided according to the situation, and the parameter combinations can be as follows:

Since the parameters are uncertain during construction, you can create a constructor Builder for it to separate the construction process and representation of SqlSessionFactory:

MyBatis makes SqlSessionFactoryBuilder and SqlSessionFactory independent of each other. First get a SqlSessionFactoryBuilder object through the builder() method, and then build a SqlSessionFactory object through the SqlSessionFactoryBuilder object, reflecting the application of the constructor mode.

4.2 creation of database connection Environment object

In the process of building the Configuration object, when XMLConfigParser parses the < environment > node of the mybatis XML Configuration file, it will have the following corresponding code:

  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");
        //When it is the same as the default environment, resolve it
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
 
          //The built-in constructor Builder of Environment is used to pass id transaction factory and data source
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

Inside the Environment, a static Builder class is defined:

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

5, Epilogue

"Blade out of sheath, Mybatis initialization", completed.

Make progress every day!!!

Posted by ghostdog74 on Thu, 07 Oct 2021 17:00:31 -0700