In-depth introduction to Mybatis series three-configuration details of properties and environments (mybatis source code)

Posted by leetcrew on Fri, 24 May 2019 23:45:21 +0200

This article is reproduced from Nan Ke Meng

In the previous article, Mybatis Series (2) - Introduction to Configuration (Mybatis Source Chapter), through a simple analysis of mybatis source code, we can see that in the mybatis configuration file, under the configuration root node, configurable properties, type Aliases, plugins, objectFactory, objectWrapperFactory, settings, environments, database IdProvider, type Hand can be configured. The nodes are lers and mappers. Then this time, we will first introduce the properties node and environment node.

In order to enable you to better read the mybatis source code, I would like to give you a simple example of the use of properties.

properties node

<configuration>
Method 1: Specify the properties configuration file from the outside, in addition to using the resource attribute to specify, you can also specify the url through the url attribute.  
    <properties resource="dbConfig.properties"></properties> 
  -->
  Method 2: Configure XML directly
  <properties>
      <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"/>
  </properties>

So, if I use both methods at the same time, which method is the priority?
When the above two methods are xml configuration first, the external specified properties configuration second. As for why, the following source code analysis will mention, please pay attention to it.

Let's look at how envirements element nodes are used.

envirements node

<environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <!--
          //If there is no property file specifying the database configuration above, 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 database configuration file is specified above, and these four attributes correspond to each other in the configuration file. -->
         <property name="driver" value="${driver}"/>
         <property name="url" value="${url}"/>
         <property name="username" value="${username}"/>
         <property name="password" value="${password}"/>
         
      </dataSource>
    </environment>
    
    <!-- I'll appoint another one. environment -->
    <environment id="test">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <!-- And above url Dissimilarity -->
        <property name="url" value="jdbc:mysql://localhost:3306/demo"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
      </dataSource>
    </environment>
    
  </environments>

Environment element nodes can configure multiple environment sub-nodes. How do you understand that?

If the development environment of our system is different from the database used in the formal environment (which is affirmative), then we can set up two environments, two IDS correspond to the development environment (dev) and the formal environment (final), then we can select the corresponding environment by configuring the default attribute of the environment. For example, I configure the value of the deault attribute of the environment to be configured. Dev, then the environment of dev is selected. As for how this is achieved, the following source code will be sa id.

Well, here's a brief introduction to the configuration of properties and environments, and then we'll start looking at the source code formally.

Last time we said that mybatis is parsing mybatis configuration files through the class XMLConfigBuilder, so this time we'll look at XMLConfigBuilder's parsing of properties and environments.

XMLConfigBuilder source code

public class XMLConfigBuilder extends BaseBuilder {

    private boolean parsed;
    //xml parse
    private XPathParser parser;
    private String environment;
  
    /**
    * Last time I mentioned this method, I parsed the element nodes that can be configured in my Batis configuration file.
    * The first thing to look at today is the properties node and the environments node
    */
    private void parseConfiguration(XNode root) {
        try {
          //Parsing properties elements
      //issue #117 read properties first
          propertiesElement(root.evalNode("properties")); 
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          settingsElement(root.evalNode("settings"));

          //Parsing environment elements
      // 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);
        }
    }
  
    
    //Here's how to parse properties
    private void propertiesElement(XNode context) throws Exception {
        if (context != null) {
          /**
          * set the name and value attributes of the child node into the properties object
          * Here's the order in which xml configuration takes precedence and external property configuration takes precedence.
          */
          Properties defaults = context.getChildrenAsProperties();
          //Get the value of the resource attribute on the properties node
          String resource = context.getStringAttribute("resource");
          //Get the value of the url attribute on the properties node, and the resource and url cannot be configured at the same time
          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.");
          }
          //set the parsed properties file into the Properties object
          if (resource != null) {
            defaults.putAll(Resources.getResourceAsProperties(resource));
          } else if (url != null) {
            defaults.putAll(Resources.getUrlAsProperties(url));
          }

          /**
          * Fusion of the Properties of the configuration object that have been configured with the Properties that have just been parsed
          * configuration This object loads all the node elements of the parsed mybatis configuration file.
          * This object will also be mentioned frequently in the future, since the configuration object uses a series of get/set methods.
          * Does that mean we can configure it directly with java code? 
          * The answer is yes, but the advantage of using configuration files for configuration is self-evident.
          */
          Properties vars = configuration.getVariables();
          if (vars != null) {
            defaults.putAll(vars);
          }
          //set the parsing configuration properties object into the parser, because it may be used later
          parser.setVariables(defaults);
          //set into the configuration object
          configuration.setVariables(defaults);
        }
    }
    
    //Now let's look at the method of parsing enviroments element nodes
    private void environmentsElement(XNode context) throws Exception {
        if (context != null) {
            if (environment == null) {
                //Resolving the default attribute values of environments nodes
                //For example: <environments default="development">
                environment = context.getStringAttribute("default");
            }
            //Recursive parsing environments subnodes
            for (XNode child : context.getChildren()) {
                /**
                * <environment id="development">, Only enviroment nodes have id attributes.
                * So what does this attribute do? There can be multiple environment sub-nodes under the environments node
                * Similarly: 
                *          <environments default="development">
                *               <environment id="development">...</environment>
                *               <environment id="test">...</environment>
                *          </environments>
                * This means that we can correspond to multiple environments, such as development environment, test environment, etc.
                * Select the corresponding enviroment from the default attribute of environments
                */
                String id = child.getStringAttribute("id");
                //isSpecial is to select the corresponding enviroment according to the default attribute of environments.
                if (isSpecifiedEnvironment(id)) {
                    /**
                    * There are two kinds of transactions in mybatis: JDBC and MANAGED. When configuring JDBC, JDBC uses JDBC transactions directly.
            * Configuration of MANAGED is to host transactions to containers
                    */
                    TransactionFactory txFactory 
                        =transactionManagerElement(child.evalNode("transactionManager"));
                    /**
                    * enviroment Below the node is the dataSource node, parsing the dataSource node
                    * Here's how to parse dataSource
                    */
                    DataSourceFactory dsFactory 
            = dataSourceElement(child.evalNode("dataSource"));
                    DataSource dataSource = dsFactory.getDataSource();
                    Environment.Builder environmentBuilder=new Environment.Builder(id)
                          .transactionFactory(txFactory)
                          .dataSource(dataSource);
                    //The old rule is to set dataSource into the configuration object
                    configuration.setEnvironment(environmentBuilder.build());
                }
            }
        }
    }
    
    //Let's look at the analytical method of dataSource
    private DataSourceFactory dataSourceElement(XNode context) throws Exception {
        if (context != null) {
            //Connection pool of dataSource
            String type = context.getStringAttribute("type");
            //Subnode name, value attribute set into a properties object
            Properties props = context.getChildrenAsProperties();
            //Create dataSourceFactory
            DataSourceFactory factory 
        = (DataSourceFactory) resolveClass(type).newInstance();
            factory.setProperties(props);
            return factory;
        }
        throw new BuilderException("Environment declaration 
        requires a DataSourceFactory.");
    } 
}

Through the above interpretation of mybatis source code, I believe you have a deep understanding of the configuration of mybatis.

Another question, as we have seen above, is how to parse the expression ${driver} when configuring dataSource? Actually, it is parsed through the Property Parser class:

Property Parser source code

/**
 * This class parses expressions in the form of ${}
 */
public class PropertyParser {

  public static String parse(String string, Properties variables) {
    VariableTokenHandler handler = new VariableTokenHandler(variables);
    GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
    return parser.parse(string);
  }

  private static class VariableTokenHandler implements TokenHandler {
    private Properties variables;

    public VariableTokenHandler(Properties variables) {
      this.variables = variables;
    }

    public String handleToken(String content) {
      if (variables != null && variables.containsKey(content)) {
        return variables.getProperty(content);
      }
      return "${" + content + "}";
    }
  }
}

Well, that's the analysis of properties and environments element nodes, which are more important in the annotations to the source code. This is the end of this article, and the next article will continue to analyze the configuration of other nodes.

Topics: Python JDBC Mybatis Attribute MySQL