Beauty of Mybatis source code: 2.12. Parse the 'environments' element to complete the multi environment configuration in' Mybatis'

Posted by salathe on Sat, 27 Jun 2020 06:24:08 +0200

Parsing environment elements to complete multi environment configuration in Mybatis

After finishing the tedious process of configuring the Configuration object based on settings, it's time to parse the environments tab and configure the Mybatis multi environment.

Mybatis supports multi Environment configuration by default. There is an Environment object in mybatis, which has three simple parameters:

/**
 *  Mybatis Environment container
 *
 * @author Clinton Begin
 */
public final class Environment {
    // Environment unique sign
    private final String id;
    // Affairs factory
    private final TransactionFactory transactionFactory;
    // data source
    private final DataSource dataSource;
}

The id is the only sign of the current environment and belongs to the semantic attribute.

The transactionfactory property corresponds to the transactionfactory object, which is a Transaction creation factory used to create Transaction objects.

The Transaction object wraps the JDBC Connection to handle the life cycle of database links, including the creation, commit / rollback, and shutdown of links.

The datasource property corresponds to the datasource object and points to a JDBC data source.

The DTD definition of environments in Mybatis is as follows:

<!--ELEMENT environments (environment+)-->
<!--ATTLIST environments
default CDATA #REQUIRED
-->

The value of the default attribute must be specified in environments. It is the ID of the default environment, which is used to specify the environment configuration used by default. At the same time, in environments One or more environment child elements are allowed in.

<!--ELEMENT environment (transactionManager,dataSource)-->
<!--ATTLIST environment
id CDATA #REQUIRED
-->

The environment element has a required ID attribute, which is the unique flag of the current environment configuration. At the same time, it must configure a transaction manager and dataSource child element.

transactionManager

Transaction manager is used to configure the transaction manager of the current environment. Its DTD is defined as follows:

<!--ELEMENT transactionManager (property*)-->
<!--ATTLIST transactionManager
type CDATA #REQUIRED
-->

transactionManager has a required type attribute, which indicates the type of transaction manager used. In Mybatis, two types of transaction managers are provided by default Manager: JDBC and MANAGED. These two short names rely on the type aliasing mechanism of Mybatis, and they are registered in the parameterless construction method of Configuration:

public Configuration() {
    // Register alias

    // Register JDBC alias
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    // Register transaction management alias
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
    ...
    }

The JdbcTransaction object created by the JdbcTransactionFactory corresponding to JDBC directly uses the default JDBC commit and rollback settings, and relies on the link obtained from the data source to manage the transaction scope.

The ManagedTransaction object created by the ManagedTransactionFactory corresponding to MANAGED is one of the implementation of Transaction. It ignores the submission and rollback requests, but responds to the request to close the connection. However, we can control how to handle the request to close the connection by using the closeConnection parameter, When the value of closeConnection is false, it will ignore the request to close the link.

<transactionmanager type="MANAGED">
  <property name="closeConnection" value="false" />
</transactionmanager>

In the above example, we can see that the transaction manager also allows the configuration of the property tag for custom transaction manager parameters.

dataSource

The dataSource element is used to configure the JDBC data source. Its DTD is defined as follows:

<!--ELEMENT dataSource (property*)-->
<!--ATTLIST dataSource
type CDATA #REQUIRED
-->

The dataSource label also has a required type attribute, which is used to point to specific instances of JDBC data sources. By default, there are three types of data sources in Mybatis: UNPOOLED,POOLED and JNDI.

Similarly, these three short names are also type aliases of Mybatis, whose registration is also in the nonparametric construction of the Configuration object:

public Configuration() {
    // Register alias
    
    ...
    
    // Register JNDI alias
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    // Register pooled data source alias
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    // Register as a pooled data source
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
    
    ...
    
    }

The JndiDataSourceFactory corresponding to JNDI is used to load an available DataSource based on JNDI.

POOLED corresponding PooledDataSourceFactory is used to get a PooledDataSource.

PooledDataSource is a simple, synchronous and thread safe database Connection pool. By reusing JDBC Connection, the initialization and authentication time necessary for repeatedly creating a new linked instance is avoided, and the concurrent ability of application program to database access is improved.

UNPOOLED corresponding UnpooledDataSourceFactory is used to obtain an UnpooledDataSource. UnpooledDataSource is a simple data source. It will open a new link for each request to obtain a link.

Multiple property sub tags are allowed under the DataSource tag. These Properties will be converted into Properties to initialize and configure the corresponding DataSource After understanding the role of each element tag, let's go back to the code that parses the environments.

Call resolved entry (XmlConfigBuilder):

private void parseConfiguration(XNode root) {
    // ...
    // Load the multi environment source configuration to find the transaction manager and data source corresponding to the current environment (default)
    environmentsElement(root.evalNode("environments"));
   // ...
}

Parse and build the Environment object of Mybatis:

/**
 * Analyze the environments node
 *
 * @param context environments node
 */
private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
        if (environment == null) {
            // Configure default environment
            environment = context.getStringAttribute("default");
        }
        for (XNode child : context.getChildren()) {
            // Get environment unique flag
            String id = child.getStringAttribute("id");
            if (isSpecifiedEnvironment(id)) {
                // Configure default data source
                // Create transaction factory
                TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
                // Create data source factory
                DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
                // create data source
                DataSource dataSource = dsFactory.getDataSource();

                // Build an environment container through environment unique flag, transaction factory and data source
                Environment.Builder environmentBuilder = new Environment.Builder(id)
                        .transactionFactory(txFactory)
                        .dataSource(dataSource);

                // Configure the current environment container of Mybatis
                configuration.setEnvironment(environmentBuilder.build());
            }
        }
    }
}

The process of parsing the environment tag is not very complicated. First, it obtains the user specified default environment flag through the default attribute of environment.

Then, all environment nodes under environments are traversed, and the unique flag of the environment to be processed is obtained through the id attribute of the environment node,

If the current flag is the same as the default environment flag specified by the user, the environment node will be processed, otherwise the processing will be skipped.

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

To process the operation of the environment node, we mainly obtain the corresponding TransactionFactory by parsing the elements transactionManager and dataSource Object and the DataSourceFactory object.

The parsing process of dataSource elements is almost the same as that of transactionManager. First, the actual type corresponding to the type attribute is resolved, and then the object instance is obtained by reflection. Finally, the setProperties() method of the instance object is invoked to complete the synchronization of the custom parameters.

About the resolution process of transactionManager:

    /**
     * Configure the transaction management mechanism according to the environments & gt; Environment & gt; transactionmanager node
     *
     * @param context environments&gt;environment&gt;transactionManager Node content
     */
    private TransactionFactory transactionManagerElement(XNode context) throws Exception {
        if (context != null) {
            // Get transaction type
            String type = context.getStringAttribute("type");
            // Get transaction parameter configuration
            Properties props = context.getChildrenAsProperties();
            // Create transaction factory
            TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
            // Set custom parameters
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration requires a TransactionFactory.");
    }

About the parsing process of dataSource:

    /**
     * Parsing dataSource elements
     */
    private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
            // Gets the DataSourceFactory alias that the type property of the dataSource points to.
            String type = context.getStringAttribute("type");
            // Get the parameter set of user-defined configuration
            Properties props = context.getChildrenAsProperties();
            // The specific type corresponding to the alias is resolved, and the entity is obtained by reflection.
            DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
            // Set value
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration requires a DataSourceFactory.");
    }

Unlike TransactionFactory, after DataSourceFactory is initialized, Mybatis will call the getDataSource() method of DataSourceFactory to get the specific DataSource instance.

Now that you have all the necessary elements to configure the Environment object, build one by using the builder of the Environment object Environment object and synchronize the environment instance to the environment property in the Configuration class Configuration of Mybatis.

At this point, the whole environment element parsing has been completed.

Let's take a look at TransactionFactory and DataSourceFactory before we move on to parsing the following elements.

First let's look at TransactionFactory:

/**
 * An instance used to create a Transaction object
 *
 * @author Clinton Begin
 */
public interface TransactionFactory {

    /**
     * Configure custom properties for the transaction factory
     * @param props Custom properties
     */
    void setProperties(Properties props);

    /**
     * Create a transaction through an existing link
     * @param conn Existing database links
     * @return Transaction affair
     * @since 3.1.0
     */
    Transaction newTransaction(Connection conn);

    /**
     * Create a transaction from the specified data source
     * @param dataSource data source
     * @param level Transaction level
     * @param autoCommit Auto submit or not
     * @return Transaction affair
     * @since 3.1.0
     */
    Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}

From the method definition, we can see that TransactionFactory is the factory instance that creates the Transaction object. In mybatis, the Transaction object encapsulates the native Connection object, Takes over control of Connection objects, including creating links, committing transactions, rolling back transactions, closing links, and obtaining transaction timeout.

/**
 * Wrap database connection.
 * Handle connection lifecycle, including: create, prepare, commit / roll back and close
 *
 * @author Clinton Begin
 */
public interface Transaction {

    /**
     * Get a database link
     *
     * @return Database links
     */
    Connection getConnection() throws SQLException;

    /**
     * Submit
     */
    void commit() throws SQLException;

    /**
     * RollBACK 
     */
    void rollback() throws SQLException;

    /**
     * Close link
     */
    void close() throws SQLException;

    /**
     * Get transaction timeout
     */
    Integer getTimeout() throws SQLException;

}

There are two default implementations of TransactionFactory: JdbcTransactionFactory and ManagedTransactionFactory.

The JdbcTransactionFactory is responsible for creating an instance of the JdbcTransaction object, and the ManagedTransactionFactory is responsible for creating an instance of the ManagedTransaction object.

>Factories and products like this are defined by abstract factories, which are usually called abstract factories, corresponding to the abstract factory pattern in the design pattern.

The implementation of JdbcTransactionFactory is relatively simple. It just calls different construction methods of JdbcTransaction to complete the initialization of JdbcTransaction object. At the same time, it does not handle the user-defined property configuration.

/**
 * Create an instance of JdbcTransaction
 *
 * @author Clinton Begin
 *
 * @see JdbcTransaction
 */
public class JdbcTransactionFactory implements TransactionFactory {

  @Override
  public void setProperties(Properties props) {
  }

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

The slightly different thing about ManagedTransactionFactory is that it reads the custom property called closeConnection passed in by the user and uses it to initialize the ManagedTransaction object when creating the ManagedTransaction object instance.

/**
 * Create a ManagedTransactionFactory instance
 *
 * @author Clinton Begin
 *
 * @see ManagedTransaction
 */
public class ManagedTransactionFactory implements TransactionFactory {

  private boolean closeConnection = true;

  @Override
  public void setProperties(Properties props) {
    if (props != null) {
      String closeConnectionProperty = props.getProperty("closeConnection");
      if (closeConnectionProperty != null) {
        closeConnection = Boolean.valueOf(closeConnectionProperty);
      }
    }
  }

  @Override
  public Transaction newTransaction(Connection conn) {
    return new ManagedTransaction(conn, closeConnection);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    // Silently ignores autocommit and isolation level, as managed transactions are entirely
    // controlled by an external manager.  It's silently ignored so that
    // code remains portable between managed and unmanaged configurations.
    return new ManagedTransaction(ds, level, closeConnection);
  }
}

The implementation of the JdbcTransaction function depends on the link obtained from the data source. It will directly delegate the submission and rollback requests to the JDBC native Connection object to complete.

As another implementation of Transaction, ManagedTransaction is different from JdbcTransaction, When the operation request for the link arrives, ManagedTransaction will ignore all submission and rollback requests by default. Meanwhile, for the request to close the link, it will decide whether to accept or not according to the closeConnection passed in when creating the ManagedTransaction object.

Then let's take a look at the class diagram related to DataSourceFactory:

In the previous section, we learned about the general functions of different implementation classes of DataSourceFactory, because the knowledge related to database connection pool is involved here, so more detailed information related to DataSourceFactory will be given when expanding the knowledge related to database connection pool.

At this point, we have basically completed the processing of the environment element.

Pay attention to me and learn more together

Topics: Programming Mybatis JDBC Attribute Database