MyBatis source code parsing: building sqlSessionFactory

Keywords: xml Mybatis Session SQL

 

public static void main(String[] args) throws IOException {
    String resource = "mybatis-config.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(inputStream);
    try (SqlSession session = sqlSessionFactory.openSession()) {
     //Mode 1:
      UserMapper mapper = session.getMapper(UserMapper.class);
      User user1 = mapper.selectBlog("1");

      //Mode 2:
      User user2 = session.selectOne(
        "test.UserMapper.selectBlog", 1);
    }
  }

1. Constructing sqlSessionFactory

Read and parse MyBatis configuration file mybatis-config.xml, and save the parsed node data to the Configuration object.

Constructing sqlSessionFactory through Configuration Object

//Read configuration files
InputStream inputStream =Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory =
      new SqlSessionFactoryBuilder().build(inputStream)

 

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      /**
       * 1.1 Read the mybatis-config.xml file and convert the XML file to the Document object
       */
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      /**
       * 1.2 Method parser.parse(), parse the Document, save the data to Configuration, and return to Configuration
       * 
       * 1.3 Build SqlSessionFactory through build(configuration)
       */
      return build(parser.parse());
    } 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.
      }
    }
  }

1.1. Conversion of xml files to Document objects

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    /**
     * 1.1.1 Create the configuration information saving class configuration in XMLConfigBuilder
     */
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

1.2 method parser.parse(), parses Document, saves data to Configuration, and returns Configuration

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // Parse from root node < configuration >
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
private void parseConfiguration(XNode root) {
    try {
      /**
       * Comparing with mybatis-configuration.xml, the corresponding node labels are parsed separately.
       */
      // 1.2.1 parsing properties nodes
      propertiesElement(root.evalNode("properties"));
      //1.2.2 Parsing setting Node
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      // 1.2.3 Resolution of typeAliases aliases
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //1.2.2.1 Save setting node to configuration
      settingsElement(settings);
      //1.2.4 Environment node contains data source parsing and transaction configuration parsing
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //1.2.5 Resolving typeHandlers Type Processors
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 1.2.6 parsing Mapper mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

1.2.1 Parsing properties nodes

<!-- resource External file attributes override child nodes property information -->
<properties resource="jdbc.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>
<!-- url External file attributes override child nodes property information -->
<properties url="jdbc.properties">
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

 

 /**
   * 1.2.1  Parsing properties nodes
   * @param context
   * @throws Exception
   */
  private void propertiesElement2(XNode context) throws Exception {
    if (context != null) {

      //Properties inherit Hashtable. First load the property child node name value attribute. Put in Hashtable
      Properties defaults = context.getChildrenAsProperties();

      //Read the properties resource and url in the properties node
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");

      //If both url and resource exist, an exception is 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 (resource != null) {

        //The name value of the external file introduced by the resource attribute overwrites the information of the nodes of Properties.
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {

        //The name value of the external file introduced by the url attribute overwrites the information of the nodes of Properties.
        defaults.putAll(Resources.getUrlAsProperties(url));
      }

      //Read the variables attribute information in the Configuration object and, if so, add it to the properties object
      Properties vars = configuration.getVariables();
      if (vars != null) {
        defaults.putAll(vars);
      }

      //Setting data in Properties to variables in XPathParser
      parser.setVariables(defaults);

      //Setting data in Properties to variables in configuration
      configuration.setVariables(defaults);
    }
  }

1.2.2 Parsing setting Node

<settings>
        <! - Open the cache - >
        <setting name="cacheEnabled" value="true"/>
        <! - Delayed loading - >
        <setting name="lazyLoadingEnabled" value="true"/>
        <! -- A single statement returns multiple result sets - >
        <setting name="multipleResultSetsEnabled" value="true"/>
        <! - Column labels instead of column names - >
        <setting name="useColumnLabel" value="true"/>
        <! - JDBC is not allowed to support automatic generation of primary keys - >
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="autoMappingBehavior" value="PARTIAL"/>
        <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
        <! -- Configure the defau lt executor.
        SIMPLE is a common actuator.
        The REUSE executor reuses prepared statements.
        BATCH executors reuse statements and perform batch updates - >
        <setting name="defaultExecutorType" value="SIMPLE"/>
        <! -- Timeout, which determines the number of seconds the driver waits for the database response. >
        <setting name="defaultStatementTimeout" value="25"/>
        <! -- Set a prompt value for the fetchSize of the driven resu lt set. This parameter can only be overridden in query settings. >
        <setting name="defaultFetchSize" value="100"/>
        <setting name="safeRowBoundsEnabled" value="false"/>
        <setting name="mapUnderscoreToCamelCase" value="false"/>
        <setting name="localCacheScope" value="SESSION"/>
        <setting name="jdbcTypeForNull" value="OTHER"/>
        <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
    </settings>
/**
   * 1.2.2 Resolving setting Node
   * @param context
   * @return
   */
  private Properties settingsAsProperties(XNode context) {
    if (context == null) {
      return new Properties();
    }
    /**
     * Resolve the child node property of the setting node: name value and save it to Properties (Hashtable)
     */
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    for (Object key : props.keySet()) {
      if (!metaConfig.hasSetter(String.valueOf(key))) {
        throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
      }
    }
    return props;
  }
/**
   * 1.2.1.1 Save the setting node to configuration
   * @param props
   */
  private void settingsElement(Properties props) {
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

1.2.3 Resolution of typeAliases aliases

Aliases exist only to reduce redundancy in class fully qualified names

<typeAliases>
        <typeAlias alias="Author" type="domain.blog.Author"/>
        <typeAlias alias="Blog" type="domain.blog.Blog"/>
        <typeAlias alias="Comment" type="domain.blog.Comment"/>
        <typeAlias alias="Post" type="domain.blog.Post"/>
        <typeAlias alias="Section" type="domain.blog.Section"/>
        <typeAlias alias="Tag" type="domain.blog.Tag"/>
        <! - These two tags can coexist. But the < type Aliases /> tag must be in front of the < package /> tag.
        Because a class can have multiple aliases, both label settings are valid at this time. >
        <package name="domain.blog"/>
    </typeAliases>
/**
   * 1.2.3 Resolving typeAliases aliases
   * @param parent
   */
  private void typeAliasesElement(XNode parent) {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        //These two tags can coexist. But < type Aliases />
        // The label must be in front of the <package/> label.
        // Because a class can have multiple aliases, both label settings are valid at this time.
        if ("package".equals(child.getName())) {
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }

configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);

Every Java Bean in the package domain. blog, without annotations, uses a lowercase unqualified class name of the bean as its alias. For example, the alias of domain.blog.Author is author; if there are annotations, the alias is its annotation value. See the following example:

@Alias("author")
public class Author {
    ...
}

MyBatis provides built-in type aliases for common Java types. They are case-insensitive. For example _byte, byte_long, long

1.2.4 Data Source and Transaction Analysis in the next section

1.2.5 parse type handlers (complete conversion between Java data types and database data types)

    <typeHandlers>
        <typeHandler handler="org.mybatis.example.ExampleTypeHandle" javaType="VARCHAR" jdbcType="String"></typeHandler>
    </typeHandlers>
public class ExampleTypeHandler extends BaseTypeHandler<String> {

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
    ps.setString(i, parameter);
  }

  @Override
  public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
    return rs.getString(columnName);
  }

  @Override
  public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    return rs.getString(columnIndex);
  }

  @Override
  public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    return cs.getString(columnIndex);
  }
}
 

1.2.6 Parsing Mapper Mapper Mapper

    <! - Four ways to configure xml Mapping Files - >
    <! - Use resource references relative to class paths - >
    <mappers>
        <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
    </mappers>
    <! - Use fully qualified resource locators (URL s) -->
    <mappers>
        <mapper url="file:///var/mappers/AuthorMapper.xml"/>
    </mappers>
    <! - Fully qualified class names using mapper interfaces
    <mappers>
        <mapper class="org.mybatis.builder.AuthorMapper"/>
    </mappers>
    <! - Register all the mapper interfaces in the package as mappers - >.
    <mappers>
        <package name="org.mybatis.builder"/>
    </mappers>
 private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        /**
         * Parsing package node
         */
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          /**
           * resource,url,class Only one of the three tags has value, and the other two are null, can they be executed properly.
           */
          //Find the xxDao.xml Mapping file through resource
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //Parsing x xxDao.xml mapping file
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            //Find the xxDao.xml Mapping file by url
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            //Parsing x xxDao.xml mapping file
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

Through the resource, the URL finds the xxDao.xml Mapping file, continues to parse its nodes, and the parsed results are stored in the configuration object.

/**
 * Parsing xxxDao.xml file
 * @param context
*/
private void configurationElement2(XNode context) {
    try {
      //Read namespaces
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //Parsing cache-ref Tags
      cacheRefElement(context.evalNode("cache-ref"));
      //Parsing cache tags
      cacheElement(context.evalNode("cache"));
      //Parsing/mapper/parameterMap Tags
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //Resolution/mapper/resultMap Tags
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //Parsing / mapper/sql Tags
      sqlElement(context.evalNodes("/mapper/sql"));
      //Resolve the select|insert|update|delete tag
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

1.3 Build SqlSessionFactory through build(configuration)

Previously, we parsed the nodes in mybatis-config.xml file, saved the parsed data to configuration, and returned to configuration.

 /**
   * 1.3 Build SqlSessionFactory through build(configuration)
   * @param config
   * @return
   */
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

Summary:

Read and parse MyBatis configuration file mybatis-config.xml, and save the parsed node data to the Configuration object.

Constructing sqlSessionFactory through Configuration Object

Posted by kiowa_jackson on Sat, 24 Aug 2019 02:05:55 -0700