[MyBatis Source Analysis] Configuration Loading (Part II)

Posted by andrests on Tue, 02 Jul 2019 22:23:21 +0200

Element Settings

Continue MyBatis Configuration loading source code analysis:

 1 private void parseConfiguration(XNode root) {
 2     try {
 3       Properties settings = settingsAsPropertiess(root.evalNode("settings"));
 4       //issue #117 read properties first
 5       propertiesElement(root.evalNode("properties"));
 6       loadCustomVfs(settings);
 7       typeAliasesElement(root.evalNode("typeAliases"));
 8       pluginElement(root.evalNode("plugins"));
 9       objectFactoryElement(root.evalNode("objectFactory"));
10       objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
11       reflectorFactoryElement(root.evalNode("reflectorFactory"));
12       settingsElement(settings);
13       // read it after objectFactory and objectWrapperFactory issue #631
14       environmentsElement(root.evalNode("environments"));
15       databaseIdProviderElement(root.evalNode("databaseIdProvider"));
16       typeHandlerElement(root.evalNode("typeHandlers"));
17       mapperElement(root.evalNode("mappers"));
18     } catch (Exception e) {
19       throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
20     }
21 }

Last time I saw the analysis of the < type Alias > label in line 7, I skipped the sections of <plugins>, <objectFactory>, <objectWrapperFactory>, <reflectorFactory>, <type Handlers>, <database IdProvider> for the time being, which either belong to the less commonly used parts of MyBatis or to the more advanced applications in MyBatis. And then again.

Now let's look at the code for element settings, the settings Element method on line 12:

 1 private void settingsElement(Properties props) throws Exception {
 2     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
 3     configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
 4     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
 5     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
 6     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
 7     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
 8     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
 9     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
10     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
11     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
12     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
13     configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
14     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
15     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
16     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
17     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
18     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
19     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
20     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
21     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
22     configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), false));
23     configuration.setLogPrefix(props.getProperty("logPrefix"));
24     @SuppressWarnings("unchecked")
25     Class<? extends Log> logImpl = (Class<? extends Log>)resolveClass(props.getProperty("logImpl"));
26     configuration.setLogImpl(logImpl);
27     configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
28 }

To see the implementation of this method is to set the contents of <settings> parsed before into Configuration. As if I had forgotten to mention one thing, Configuration is an attribute of BaseBuilder, the parent class of XML ConfigBuilder. There are three important attributes stored in BaseBuilder. Draw a graph to show them:

 

Environment loading

Next comes the loading of <environments>, a more important attribute for configuring JDBC information, which corresponds to the code of environmentsElement(root.evalNode("environments"):

 1 private void environmentsElement(XNode context) throws Exception {
 2     if (context != null) {
 3       if (environment == null) {
 4         environment = context.getStringAttribute("default");
 5       }
 6       for (XNode child : context.getChildren()) {
 7         String id = child.getStringAttribute("id");
 8         if (isSpecifiedEnvironment(id)) {
 9           TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
10           DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
11           DataSource dataSource = dsFactory.getDataSource();
12           Environment.Builder environmentBuilder = new Environment.Builder(id)
13               .transactionFactory(txFactory)
14               .dataSource(dataSource);
15           configuration.setEnvironment(environmentBuilder.build());
16         }
17       }
18     }
19 }

Lines 3 to 5 get the default JDBC environment name.

Line 6 begins traversing every < environment > tag under the < environment > tag. Line 7 first gets the id attribute under < environment > and then line 8 decides whether the current < environment > is the default JDBC environment, that is, the default attribute corresponding to the value obtained by line 3 to line 5. Two problems can be seen from this code:

  1. The source code does not judge the scenario that does not satisfy the judgement of line 8, that is, the default attribute under the < environments > tag is a mandatory attribute.
  2. Even if you configure more < environment > tags, MyBatis will only load one of them < environment >

Line 9 of the code retrieves the transaction manager based on the <transaction Manager> tag. This series of articles is configured with "JDBC". Then the instantiated Jdbc Transaction Factory (JDBC - > Jdbc Transaction Factory's corresponding relationship is in alias mapping of the Configuration constructor configuration) and the other is ManagedTransaction Facto. Ry and Spring Managed Transaction Factory, the former is supported by MyBatis native, and the latter is supported by Spring framework.

Line 10 is similar to line 9. Data Source Factory is obtained from the < dataSource > tag. This series of articles is configured with "POOLED", then the corresponding relationship of Pooled Data Source Factory (POOLED - > Pooled Data Source Factory) is instantiated by alias mapping in Configuration constructor configuration. Among others are Unpooled Data Source Factory and JndiData Source Factory.

Line 11 gets the DataSource according to the DataSourceFactory, and in MyBatis there are three scenarios according to configuration:

  • The DataSource corresponding to the Pooled Data Source Factory is the Pooled Data Source.
  • Unpooled Data Source Factory corresponds to Unpooled Data Source Factory.
  • The corresponding DataSource of JndiDataSourceFactory is to be found in the JNDI service.

Lines 12 to 15 are fairly simple. Create an Environment based on TransactionFactory and DataSource and set it to Configuration.

 

mapper loading

The two most important tags in config.xml are <environment> (JDBC environment information) and mapper (sql file mapping). The mapper load is "mapperElement(root.evalNode("mappers ")" this code, look at the implementation:

 1 private void mapperElement(XNode parent) throws Exception {
 2     if (parent != null) {
 3       for (XNode child : parent.getChildren()) {
 4         if ("package".equals(child.getName())) {
 5           String mapperPackage = child.getStringAttribute("name");
 6           configuration.addMappers(mapperPackage);
 7         } else {
 8           String resource = child.getStringAttribute("resource");
 9           String url = child.getStringAttribute("url");
10           String mapperClass = child.getStringAttribute("class");
11           if (resource != null && url == null && mapperClass == null) {
12             ErrorContext.instance().resource(resource);
13             InputStream inputStream = Resources.getResourceAsStream(resource);
14             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
15             mapperParser.parse();
16           } else if (resource == null && url != null && mapperClass == null) {
17             ErrorContext.instance().resource(url);
18             InputStream inputStream = Resources.getUrlAsStream(url);
19             XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
20             mapperParser.parse();
21           } else if (resource == null && url == null && mapperClass != null) {
22             Class<?> mapperInterface = Resources.classForName(mapperClass);
23             configuration.addMapper(mapperInterface);
24           } else {
25             throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
26           }
27         }
28       }
29     }
30 }

See < mappers > can be defined under < mapper > and < package > two sub-labels, they are also two-choice relationship, that is, can only define one of them. Here we first look at the content of package branch, that is, loading Mapper according to the class path, but not looking at the content of else branch, that is, parsing sql according to < mapper > label. Mapping.

Then line 8 to line 10 retrieves each resource, url, and mapperClass in <mapper>, and the following judgment is interesting:

  • resource != null && url == null && mapperClass == null
  • resource == null && url != null && mapperClass == null
  • resource == null && url == null && mapperClass != null

This tells us that only one of the three attributes of resource, url and mapperClass can be defined. The exception thrown in the else branch also confirms this statement. The examples in this series define resource and the most common way to define resource is to enter the first if judgment.

It's not very important to set the resource in line 12's code context.

Line 13 gets InputStream based on the mapper file path, and InputStream will then be converted to InputSource to parse the mapper file.

Line 14 takes an XML Mapper Builder, which has the same process as the XML Config Builder analyzed above, and uses XPath Parser to parse the mapper file into Document.

The code in line 15 follows up on the implementation because the parse method of the XML MapperBuilder is different from the parse method of the XML ConfigBuilder. After all, two MyBatis profiles are parsed:

 1 public void parse() {
 2     if (!configuration.isResourceLoaded(resource)) {
 3       configurationElement(parser.evalNode("/mapper"));
 4       configuration.addLoadedResource(resource);
 5       bindMapperForNamespace();
 6     }
 7 
 8     parsePendingResultMaps();
 9     parsePendingChacheRefs();
10     parsePendingStatements();
11 }

Line 2 determines whether the current resource has been loaded or not, and if it has not been loaded, lines 3 to 5 will be executed.

First, line 3 of the code configurationElement:

 1 private void configurationElement(XNode context) {
 2     try {
 3       String namespace = context.getStringAttribute("namespace");
 4       if (namespace == null || namespace.equals("")) {
 5         throw new BuilderException("Mapper's namespace cannot be empty");
 6       }
 7       builderAssistant.setCurrentNamespace(namespace);
 8       cacheRefElement(context.evalNode("cache-ref"));
 9       cacheElement(context.evalNode("cache"));
10       parameterMapElement(context.evalNodes("/mapper/parameterMap"));
11       resultMapElements(context.evalNodes("/mapper/resultMap"));
12       sqlElement(context.evalNodes("/mapper/sql"));
13       buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
14     } catch (Exception e) {
15       throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
16     }
17 }

Line 3 gets the namespace of the current mapper file. namespace is an important attribute. All the <sql>, <resultMap>, <insert>, <delete>, <update>, <select> tags have id bound to namespace to ensure global uniqueness when namespace is not defined or is a space string. The exception is thrown on line 5, so the namespace of each mapper file is a mandatory content.

The code in line 7 sets up namespace in Mapper Builder Assistant so that the following text can take namespace through Mapper Builder Assistant without having to pass a String type parameter at a time.

Lines 8 to 13 are used to parse the labels < cache-ref >, < cache >, < parameterMap >, < resultMap >, < SQL >, < select >, < insert >, < update >, < delete > respectively.

  • The cacheRefElement method is used to parse the <cache-ref> tag, which is summarized as follows:
  1. The parsed CacheRef is placed in the cacheRefMap
  2. cacheRefMap is a HashMap
  3. Located in the Configuration object
  4. Key is the namespace of the mapper file and Value is the namespace configured in <cache-ref>.
  • The cacheElement method is used to parse the < cache > tag, which is summarized as follows:
  1. An org.apache.ibatis.cache.Cache will be generated based on the new attribute configured in <cache>.
  2. Use this Cache as MyBatis cache
  • The parameterMapElement method is used to parse the <parameterMap> tag, which is summarized as follows:
  1. The parsed ParameterMap is placed in the parameterMaps
  2. parameterMaps is a Strict Map
  3. Located in the Configuration object, StrictMap is a subclass of HashMap
  4. Key is the id attribute in the namespace + "."+ < parameterMap > tag of the current mapper, and Value is the ParameterMap object
  • The resultMapElements method is used to parse the <resultMap> tag in the following summary:
  1. The ResultMap parsed is placed in resultMaps
  2. resultMaps is a Strict Map.
  3. Located in the Configuration object
  4. Key is the id attribute in the namespace + "."+ < resultMap > tag of the current mapper, and Value is the ResultMap object
  • The sqlElement method is used to parse < SQL > tags, which is summarized as follows:
  1. The parsed content is placed in SQL fragments
  2. sqlFragments is a Strict Map
  3. Located in the XML MapperBuilder object
  4. Key is the id attribute in the namespace + "."+ < SQL > tag of the current mapper, and Value is sql, the XNode itself.
  • BuilStatementFromContext is used to parse the four tags <select>, <insert>, <update>, <delete>, which are summarized as follows:
  1. The parsed content is placed in mappedStatements
  2. mappedStatements is a Strict Map
  3. Located in the Configuration object
  4. Key is the id attribute in the current mapper's namespace + "."+ < Select > < Insert > < update > < delete > tag, and Value is the MappedStatement object

 

Building SqlSessionFactory

Finally, build the SqlSessionFactory, looking back at the previous SqlSessionFactory Builder build method:

 1 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
 2     try {
 3       XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
 4       return build(parser.parse());
 5     } catch (Exception e) {
 6       throw ExceptionFactory.wrapException("Error building SqlSession.", e);
 7     } finally {
 8       ErrorContext.instance().reset();
 9       try {
10         inputStream.close();
11       } catch (IOException e) {
12         // Intentionally ignore. Prefer previous error.
13       }
14     }
15 }

The parser.parse() sentence of the fourth line method has been analyzed before, converting the configuration file into various objects defined in MyBatis and storing most of the configuration in Configuration, while a few configurations are stored in the parent BaseBuilder of the XML ConfigBuilder.

Then there's the outer build method. Look at the implementation.

 1 public SqlSessionFactory build(Configuration config) {
 2     return new DefaultSqlSessionFactory(config);
 3 }

The final SqlSessionFactory is Default SqlSessionFactory, which takes the Configuration object as its parameter.

Topics: Java Attribute SQL Mybatis xml