The previous section examined the XMLConfigBuilder source code in its entirety, and this section looks at the details. This section includes an analysis of properties and Environments in configuration files

Properties

The part in red is the entry function for parsing properties, which resolves the properties tag to XNode by calling the parsing function in xpath.

private void propertiesElement(XNode context) throws Exception {
  if(context ! =null) {
    // Get the default configuration properties
    Properties defaults = context.getChildrenAsProperties();

    // Get the resource content
    String resource = context.getStringAttribute("resource");
    // Network configuration path
    String url = context.getStringAttribute("url");

    // The two cannot coexist
    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.");
    }


    // Add the database configuration file to properties
    if(resource ! =null) {
      defaults.putAll(Resources.getResourceAsProperties(resource));
    } else if(url ! =null) {
      defaults.putAll(Resources.getUrlAsProperties(url));
    }


    Properties vars = configuration.getVariables();

    // If properties has been set before, that is, properties specified by the parameter
    if(vars ! =null) {
      // Add the previous configuration information to the current configuration
      defaults.putAll(vars);
    }

    // Go back to the configuration fileparser.setVariables(defaults); configuration.setVariables(defaults); }}Copy the code

The first step is to convert the child node to properties, which is how our node is normally configured

<! Load properties file in classpath -->
<properties resource="db.properties"/>
Copy the code

There are no child nodes. We specify an additional configuration file using the resource attribute. We can also specify the database connection information directly under the child tag, for example

<! Load properties file in classpath -->
<properties>
  <property name="driver" value="com.mysql.jdbc.Driver"/>
  <property name="url" value="jdbc:mysql://localhost:3306/mybatis_analyze"/>
  <property name="username" value="root"/>
  <property name="password" value="123"/>
</properties>
Copy the code

Not only can you use resource to specify configuration information for database files, but you can also use urls to specify network paths. However, url and resource cannot coexist, that is, you cannot specify a local path and a network path at the same time.

// Add the database configuration file to properties
if(resource ! =null) {
  defaults.putAll(Resources.getResourceAsProperties(resource));
} else if(url ! =null) {
  defaults.putAll(Resources.getUrlAsProperties(url));
}
Copy the code

A few more points need to be detailed, it is the Resources in the code above. The getResourceAsProperties (resource) and Resources. GetUrlAsProperties (url), one by one, into talking to it.

Resources.getResourceAsProperties(resource)

/** * read the file stream and load it into {@linkThe Properties} * *@paramResource Resource to be loaded *@return {@link Properties}
 * @throwsJava.io.IOException resource not found */
public static Properties getResourceAsProperties(String resource) throws IOException {
  Properties props = new Properties();
  try (InputStream in = getResourceAsStream(resource)) {
    props.load(in);
  }
  return props;
}
Copy the code

This part applies the load method in Properties to load the byte stream into Properties, where the specific implementation of getResourceAsStream can be found at juejin.cn/post/687145…

Resources.getUrlAsProperties(url)

/** * convert URL network file address to {@link Properties}
 *
 * @paramUrlString URL to be converted *@return {@link Properties}
 * @throwsJava.io.IOException Resource unreadable */
public static Properties getUrlAsProperties(String urlString) throws IOException {
  Properties props = new Properties();
  try (InputStream in = getUrlAsStream(urlString)) {
    props.load(in);
  }
  return props;
}
Copy the code

Similarly, convert the URL address file into a byte stream, and then load the byte stream using the load method in Properties.

Inside the getUrlAsStream method.

/** * convert URL file to byte stream **@paramUrlString URL to be converted *@returnByte streams {@link InputStream}
 * @throwsJava.io.IOException Network resource unreadable */
public static InputStream getUrlAsStream(String urlString) throws IOException {
  URL url = new URL(urlString);
  URLConnection conn = url.openConnection();
  return conn.getInputStream();
}
Copy the code

The specific implementation is to get the network connection, and then get the network return information.

About Configuration Coexistence

The previous section pointed out that properties can be specified either as a code parameter or as an XML configuration file, and that there will be a merge between the two, which can be seen from the source code

Properties vars = configuration.getVariables();

// If properties has been set before, that is, properties specified by the parameter
if(vars ! =null) {
  // Add the previous configuration information to the current configuration
  defaults.putAll(vars);
}
Copy the code

The properties specified by the parameter will be read first and then checked for null. The read information will be merged into the new properties and written back to the Configuration.

You can see from the source code that in the last step you put the configuration information in an xpath file. What is this asking? A little foreshadowing

Environments

Environments node parsing is a complex process that involves configuration information that is not commonly used. You will find it difficult to access environments.

environmentsElement(root.evalNode("environments"));
Copy the code

This is the entry point for parsing the Environments node, passing in its root node. Go into the code.

/**
 * 解析 environments
 *
 * @paramContext Environments Root *@throws Exception
 */
private void environmentsElement(XNode context) throws Exception {
  if(context ! =null) {

    // Check whether the environment passed to the current argument is null
    if (environment == null) {
      // If null is used, the environment specified in the configuration file is obtained
      environment = context.getStringAttribute("default");
    }

    // Walk through each of the environments
    for (XNode child : context.getChildren()) {
      String id = child.getStringAttribute("id");

      // Determine whether the default environment is equal to the current ID
      if (isSpecifiedEnvironment(id)) {
        Return to the transaction manager factory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));

        // Returns the data source
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();

        / / generated Environment
        Environment.Builder environmentBuilder = newEnvironment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); }}}}Copy the code

default

Talking about multiple environment configuration data sources, how to configure multiple configuration sources? What is the use of multiple configuration sources?

How to configure multiple configuration sources

<! Set a default connection environment -->
<environments default="mysql_developer">
    <! Connect environment information with an arbitrarily unique name -->
    <environment id="mysql_developer">
        <! Mybatis uses JDBC transaction management mode -->
        <transactionManager type="jdbc"/>
        <! Mybatis uses connection pooling to get connections -->
        <dataSource type="pooled">
            <! Configure the four necessary properties for interacting with the database
            <property name="driver" value="${mysql.driver}"/>
            <property name="url" value="${mysql.url}"/>
            <property name="username" value="${mysql.username}"/>
            <property name="password" value="${mysql.password}"/>
        </dataSource>
    </environment>

    <environment id="mysql_developer_1">
        <! Mybatis uses JDBC transaction management mode -->
        <transactionManager type="jdbc"/>
        <! Mybatis uses connection pooling to get connections -->
        <dataSource type="pooled">
            <! Configure the four necessary properties for interacting with the database
            <property name="driver" value="${mysql.driver}"/>
            <property name="url" value="${mysql.url}"/>
            <property name="username" value="${mysql.username}"/>
            <property name="password" value="${mysql.password}"/>
        </dataSource>
    </environment>
</environments>
Copy the code

Setting up multiple environments in a single environment is multiple configuration sources. However, only one configuration source can be used at each startup. This is restricted by the default in Environments, where each environment has an ID. The default attribute is then used to specify which data source to select.

What is the use of multiple configuration sources

For example, in production and development environments, the production environment needs a set of configuration sources, and the development environment needs another configuration source. Therefore, switching between the two data sources is more efficient than modification.


The first part of the code checks to see if the parameter is passed to the environment. If the parameter is not specified, the value of the default attribute in the configuration file needs to be read.

The next step is to iterate over each environment. First, obtain the ID of the environment and determine whether the default is equal to the id currently traversed. If the id is equal, the current configuration source is considered to be used.

The next step is to get the contents of the Environment node.

Transaction manager factory

Transaction manager factories are used to generate transaction managers. In MyBatis, there are two kinds of transaction manager factories

  • JdbcTransactionFactory
  • ManagedTransactionFactory

The difference between these two will be explained in a moment, but how to configure them first.

<transactionManager type="jdbc"/>
Copy the code

The above node exists inside the environment, and the type attribute of this node is configuring the transaction manager factory, which has two values

  • JDBC
  • MANAGED

Differences between the two transaction manager factories:

JDBC is implemented using JdbcTransaction objects generated by JdbcTransactionFactory. It operates on database commit and rollback in JDBC fashion.

MANAGED using ManagedTransactionFactory generated ManagedTransaction object implementation. Its commit and rollback methods do nothing, handing the transaction over to the container. By default, it closes the connection, but some containers do not want this, so you need to set the closeConnection property to false to prevent its default closing behavior. This approach is typically used in conjunction with Spring, handing transaction management to the Spring container.

<transactionManager type="MANAGED">
    <property name="closeConnection" value="false"/>
</transactionManager>
Copy the code

Both Transaction manager factories implement the TransactionFactory interface, which is a factory method pattern. We can also write our own factory, which implements the TransactionFactory interface and Transaction.

JdbcTransaction

public class JdbcTransactionFactory implements TransactionFactory {

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

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return newJdbcTransaction(ds, level, autoCommit); }}Copy the code

Inside the JdbcTransactionFactory there are actually two methods for creating a JdbcTransaction transaction manager, which we will discuss later.

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.parseBoolean(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 newManagedTransaction(ds, level, closeConnection); }}Copy the code

Inside the ManagedTransactionFactory, you can see it on the setProperties TransactionFactory implementation, so that it can be to set properties for it, The internal parsing of ManagedTransaction will follow.

Creation of the transaction manager factory

Having said two kinds of transaction manager factories, let’s talk about creating a transaction manager factory

Return to the transaction manager factory
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
Copy the code

To the interior transactionManagerElement

/** * Transaction manager factory Settings *@paramTransactionManager * in the context configuration file@returnTransaction manager *@throwsIf Exception transactionManager is null, an Exception */ is thrown
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
  if(context ! =null) {
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
Copy the code

Get the type attribute, then get the node under transactionManager and convert it to an attribute (there are child nodes only for MANAGE and name can only be closeConnection, there are no child nodes for JDBC).

The resolveClass method creates a factory based on alias reflection, which is resolved in Configuration.

The last step is to set the factory properties and return.

Data source setup

DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Copy the code

The dataSourceElement passes in the datasource property of the environment

/** * get database configuration *@paramContext configuration file dataSource *@returnData source factory@throwsIf the Exception dataSource is null, the Exception */ is thrown
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
  if(context ! =null) {
    String type = context.getStringAttribute("type");
    Properties props = context.getChildrenAsProperties();
    DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
Copy the code

Once the DataSource attribute is retrieved, the attribute value type is retrieved.

type

Data sources have the following three types

  • POOL
  • UNPOOL
  • JNDI

The difference between them is that the first one uses a database connection pool and the second one does not

The third is a special one, where the programmer, knowing that he wants to develop an application that accesses the MySQL database, codes a reference to the MySQL JDBC driver class and connects to the database by using the appropriate JDBC URL. This is the traditional approach, and it was common for non-Java programmers (such as Delphi, VB, etc.) in the past. This approach generally does not cause problems in small-scale development processes, as long as the programmer is familiar with the Java language, JDBC technology and MySQL, can quickly develop the corresponding application.

There are no problems with JNDI practices:

1. The database server name MyDBServer, user name, and password may need to be changed, causing the JDBC URL to need to be changed.

2, the database may switch to another product, such as DB2 or Oracle, causing the JDBC driver package and class name to need to change;

3. As the number of terminals in use increases, the connection pool parameters may need to be adjusted.

Solutions:

Programmers should not have to worry about “what exactly is behind the database? What is the JDBC driver? What is the JDBC URL format? What is the username and password for accessing the database?” Programmers should write programs with no references to JDBC drivers, no server names, no user names or passwords — not even database pools or connection management. Instead, these issues are left to the J2EE container (Tomcat) to configure and manage, which the programmer only needs to reference.

From there, WE have JNDI.

There are three datasourceFactories

  • JndiDataSourceFactory
  • PoolDataSourceFactory
  • UnPoolDataSourceFactory

These three data source factories will be analyzed with TransactionFactory in the next section.

Finally, alias reflection creates the data source factory and sets back the properties, the connection information.

Go back to the environmentsElement method.

The next step is to create the Environment object.

/ / generated Environment
Environment.Builder environmentBuilder = new Environment.Builder(id)
    .transactionFactory(txFactory)
    .dataSource(dataSource);
Copy the code

Environment.Builder is a built-in object for Environment, created by the internal build() method.

Finally, set the Environment back to Configuration.


This section is a bit longer and involves more details, so I hope you can be patient and deliberate.

TransactionFactory and DataSourceFactory will be analyzed in the next section.

Look at the source code for a while, has been looking at a while.