We use mybatis to operate the database through the API call of SqlSession, and create SqlSession through SqlSessionFactory. Let's take a look at the creation process of SqlSessionFactory.
Profile resolution portal
Let's look at the test method in the first article
1 public static void main(String[] args) throws IOException { 2 String resource = "mybatis-config.xml"; 3 InputStream inputStream = Resources.getResourceAsStream(resource); 4 SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); 5 SqlSession sqlSession = sqlSessionFactory.openSession(); 6 try { 7 Employee employeeMapper = sqlSession.getMapper(Employee.class); 8 List<Employee> all = employeeMapper.getAll(); 9 for (Employee item : all) 10 System.out.println(item); 11 } finally { 12 sqlSession.close(); 13 } 14 }
First, we load the configuration file with MyBatis provided tool class Resources to get an input stream. Then the SqlSessionFactory object is built by the build method of SqlSessionFactoryBuilder object. So the build method here is the entry method for us to analyze the configuration file parsing process. Let's see the code in build:
public SqlSessionFactory build(InputStream inputStream) { // Call overloaded method return build(inputStream, null, null); } public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { // Create profile parser XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // call parser.parse() Method, generate Configuration object 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. } } } public SqlSessionFactory build(Configuration config) { // Establish DefaultSqlSessionFactory,The generated Configuration afferent return new DefaultSqlSessionFactory(config); }
SqlSessionFactory is created by the build method of SqlSessionFactoryBuilder. The internal of the build method is to generate a Configuration object by parsing the mybatis-config.xml file with an XMLConfigBuilder object. XMLConfigBuilder can be seen from its name that it parses the Mybatis Configuration file. In fact, it inherits a parent class, BaseBuilder. Most of its subclasses are named after xmlxxxxx builder, that is, its subclasses are corresponding to parsing an XML file or an element in an XML file.
Let's look at the construction method of XMLConfigBuilder:
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { super(new Configuration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; }
You can see that the constructor of the parent class is called and a new Configuration() object is passed in, which is the final Mybatis configuration object.
Let's take a look at its base class, BaseBuilder.
public abstract class BaseBuilder { protected final Configuration configuration; protected final TypeAliasRegistry typeAliasRegistry; protected final TypeHandlerRegistry typeHandlerRegistry; public BaseBuilder(Configuration configuration) { this.configuration = configuration; this.typeAliasRegistry = this.configuration.getTypeAliasRegistry(); this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry(); } .... }
There are only three member variables in the BaseBuilder, and typeAliasRegistry and typeHandlerRegistry are obtained directly from the member variables of Configuration. Next, let's look at the Configuration class.
The Configuration class is located in the org.apache.ibatis.session directory of the mybatis package. Its attribute is the Configuration corresponding to the global Configuration file mybatis-config.xml of mybatis. The content in the XML Configuration is parsed and assigned to the Configuration object.
There are many XML Configuration items, so there are many properties of Configuration class. Let's take a look at the following XML Configuration file example for Configuration:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!-- Global configuration top level nodes -->
<configuration>
<!-- Attribute configuration,read properties Profile in -->
<properties resource="db.propertis">
<property name="XXX" value="XXX"/>
</properties>
<!-- Type alias -->
<typeAliases>
<!-- In use User When the type,Alias can be used directly,No input required User All paths of class -->
<typeAlias type="com.luck.codehelp.entity.User" alias="user"/>
</typeAliases>
<!-- Type processor -->
<typeHandlers>
<!-- The purpose of a type processor is to complete JDBC Type and java Conversion of type,mybatis By default, there are many types of processors,Normal without customization-->
</typeHandlers>
<!-- Object factory -->
<!-- mybatis When creating a new instance of the result object,It will be done through the object factory. mybatis There is a default object factory, which does not need to be configured normally -->
<objectFactory type=""></objectFactory>
<!-- Plug-in unit -->
<plugins>
<!-- You can customize the interceptor through plugin Tag added -->
<plugin interceptor="com.lucky.interceptor.MyPlugin"></plugin>
</plugins>
<!-- Global configuration parameters -->
<settings>
<setting name="cacheEnabled" value="false" />
<setting name="useGeneratedKeys" value="true" /><!-- Generate primary key automatically -->
<setting name="defaultExecutorType" value="REUSE" />
<setting name="lazyLoadingEnabled" value="true"/><!-- Delay load ID -->
<setting name="aggressiveLazyLoading" value="true"/><!--Whether objects with delay load attribute delay load -->
<setting name="multipleResultSetsEnabled" value="true"/><!-- Whether to allow a single statement to return multiple result sets -->
<setting name="useColumnLabel" value="true"/><!-- Use column labels instead of column names -->
<setting name="autoMappingBehavior" value="PARTIAL"/><!-- Appoint mybatis How to map columns to field properties automatically;NONE:Automatic mapping;PARTIAL:Only mapped results without nested results;FULL:Any complex result can be mapped -->
<setting name="defaultExecutorType" value="SIMPLE"/><!-- Default actuator type -->
<setting name="defaultFetchSize" value=""/>
<setting name="defaultStatementTimeout" value="5"/><!-- Timeout for the driver to wait for the database ,Units are seconds.-->
<setting name="safeRowBoundsEnabled" value="false"/><!-- Allow nested statements RowBounds -->
<setting name="safeResultHandlerEnabled" value="true"/>
<setting name="mapUnderscoreToCamelCase" value="false"/><!-- Whether the underline column name is automatically mapped to the hump attribute: for example user_id Mapping to userId -->
<setting name="localCacheScope" value="SESSION"/><!-- Local cache( session Is session level) -->
<setting name="jdbcTypeForNull" value="OTHER"/><!-- When the data is null,No specific JDBC Of type JDBC type -->
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/><!-- Specifies the method of triggering an object with delayed loading -->
<setting name="callSettersOnNulls" value="false"/><!--If setter Method or map Of put Method, if the retrieved value is null Is the data useful? -->
<setting name="logPrefix" value="XXXX"/><!-- mybatis Log file prefix string -->
<setting name="logImpl" value="SLF4J"/><!-- mybatis Implementation class of log -->
<setting name="proxyFactory" value="CGLIB"/><!-- mybatis Proxy tools -->
</settings>
<!-- Environment configuration collection -->
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/><!-- Transaction manager -->
<dataSource type="POOLED"><!-- Database connection pool -->
<property name="driver" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8" />
<property name="username" value="root" />
<property name="password" value="root" />
</dataSource>
</environment>
</environments>
<!-- mapper File mapping configuration -->
<mappers>
<mapper resource="com/luck/codehelp/mapper/UserMapper.xml"/>
</mappers>
</configuration>
For XML Configuration, the attributes of Configuration class correspond to XML Configuration. The Configuration class properties are as follows:
public class Configuration { protected Environment environment;//Running environment protected boolean safeRowBoundsEnabled = false; protected boolean safeResultHandlerEnabled = true; protected boolean mapUnderscoreToCamelCase = false; protected boolean aggressiveLazyLoading = true; //true: Any property is loaded completely when an object with delay loading property is called;false: Load each attribute as needed protected boolean multipleResultSetsEnabled = true;//Whether multiple result sets are allowed to be returned from a single statement protected boolean useGeneratedKeys = false;//Support automatic generation of primary key protected boolean useColumnLabel = true;//Use column labels or not protected boolean cacheEnabled = true;//Use cache identity or not protected boolean callSettersOnNulls = false;// protected boolean useActualParamName = true; protected String logPrefix; protected Class <? extends Log> logImpl; protected Class <? extends VFS> vfsImpl; protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION; protected JdbcType jdbcTypeForNull = JdbcType.OTHER; protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] { "equals", "clone", "hashCode", "toString" })); protected Integer defaultStatementTimeout; protected Integer defaultFetchSize; protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;//Appoint mybatis If columns are automatically mapped to fields and properties,PARTIAL Will automatically map simple results without nesting,FULL Any complex results will be mapped automatically protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; protected Properties variables = new Properties(); protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory(); protected ObjectFactory objectFactory = new DefaultObjectFactory(); protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory(); protected boolean lazyLoadingEnabled = false;//Whether to delay loading, false All associated objects are loaded,true Indicates delayed loading protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL protected String databaseId; protected Class<?> configurationFactory; protected final MapperRegistry mapperRegistry = new MapperRegistry(this); protected final InterceptorChain interceptorChain = new InterceptorChain(); protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(); protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry(); protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry(); protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"); protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection"); protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection"); protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection"); protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection"); protected final Set<String> loadedResources = new HashSet<String>(); //Already loaded resource(mapper) protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers"); protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>(); protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>(); protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>(); protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>(); protected final Map<String, String> cacheRefMap = new HashMap<String, String>(); //Other methods are omitted }
The loading process is that SqlSessionFactoryBuilder parses a Configuration object through the parse method of XMLConfigBuilder according to the file stream configured by xml. Let's take a look at its constructor.
1 public Configuration() { 2 this.safeRowBoundsEnabled = false; 3 this.safeResultHandlerEnabled = true; 4 this.mapUnderscoreToCamelCase = false; 5 this.aggressiveLazyLoading = true; 6 this.multipleResultSetsEnabled = true; 7 this.useGeneratedKeys = false; 8 this.useColumnLabel = true; 9 this.cacheEnabled = true; 10 this.callSettersOnNulls = false; 11 this.localCacheScope = LocalCacheScope.SESSION; 12 this.jdbcTypeForNull = JdbcType.OTHER; 13 this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString")); 14 this.defaultExecutorType = ExecutorType.SIMPLE; 15 this.autoMappingBehavior = AutoMappingBehavior.PARTIAL; 16 this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE; 17 this.variables = new Properties(); 18 this.reflectorFactory = new DefaultReflectorFactory(); 19 this.objectFactory = new DefaultObjectFactory(); 20 this.objectWrapperFactory = new DefaultObjectWrapperFactory(); 21 this.mapperRegistry = new MapperRegistry(this); 22 this.lazyLoadingEnabled = false; 23 this.proxyFactory = new JavassistProxyFactory(); 24 this.interceptorChain = new InterceptorChain(); 25 this.typeHandlerRegistry = new TypeHandlerRegistry(); 26 this.typeAliasRegistry = new TypeAliasRegistry(); 27 this.languageRegistry = new LanguageDriverRegistry(); 28 this.mappedStatements = new Configuration.StrictMap("Mapped Statements collection"); 29 this.caches = new Configuration.StrictMap("Caches collection"); 30 this.resultMaps = new Configuration.StrictMap("Result Maps collection"); 31 this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection"); 32 this.keyGenerators = new Configuration.StrictMap("Key Generators collection"); 33 this.loadedResources = new HashSet(); 34 this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers"); 35 this.incompleteStatements = new LinkedList(); 36 this.incompleteCacheRefs = new LinkedList(); 37 this.incompleteResultMaps = new LinkedList(); 38 this.incompleteMethods = new LinkedList(); 39 this.cacheRefMap = new HashMap(); 40 this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class); 41 this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class); 42 this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class); 43 this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class); 44 this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class); 45 this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class); 46 this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class); 47 this.typeAliasRegistry.registerAlias("LRU", LruCache.class); 48 this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class); 49 this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class); 50 this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class); 51 this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class); 52 this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class); 53 this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class); 54 this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class); 55 this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class); 56 this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class); 57 this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class); 58 this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class); 59 this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class); 60 this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class); 61 this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class); 62 this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class); 63 this.languageRegistry.register(RawLanguageDriver.class); 64 }
We see line 26 this.typeAliasRegistry = new TypeAliasRegistry();, and lines 40 to 61 register many aliases with typeAliasRegistry. Let's look at typeAliasRegistry.
public class TypeAliasRegistry { private final Map<String, Class<?>> TYPE_ALIASES = new HashMap(); public TypeAliasRegistry() { this.registerAlias("string", String.class); this.registerAlias("byte", Byte.class); this.registerAlias("long", Long.class); this.registerAlias("short", Short.class); this.registerAlias("int", Integer.class); this.registerAlias("integer", Integer.class); this.registerAlias("double", Double.class); this.registerAlias("float", Float.class); this.registerAlias("boolean", Boolean.class); this.registerAlias("byte[]", Byte[].class); this.registerAlias("long[]", Long[].class); this.registerAlias("short[]", Short[].class); this.registerAlias("int[]", Integer[].class); this.registerAlias("integer[]", Integer[].class); this.registerAlias("double[]", Double[].class); this.registerAlias("float[]", Float[].class); this.registerAlias("boolean[]", Boolean[].class); this.registerAlias("_byte", Byte.TYPE); this.registerAlias("_long", Long.TYPE); this.registerAlias("_short", Short.TYPE); this.registerAlias("_int", Integer.TYPE); this.registerAlias("_integer", Integer.TYPE); this.registerAlias("_double", Double.TYPE); this.registerAlias("_float", Float.TYPE); this.registerAlias("_boolean", Boolean.TYPE); this.registerAlias("_byte[]", byte[].class); this.registerAlias("_long[]", long[].class); this.registerAlias("_short[]", short[].class); this.registerAlias("_int[]", int[].class); this.registerAlias("_integer[]", int[].class); this.registerAlias("_double[]", double[].class); this.registerAlias("_float[]", float[].class); this.registerAlias("_boolean[]", boolean[].class); this.registerAlias("date", Date.class); this.registerAlias("decimal", BigDecimal.class); this.registerAlias("bigdecimal", BigDecimal.class); this.registerAlias("biginteger", BigInteger.class); this.registerAlias("object", Object.class); this.registerAlias("date[]", Date[].class); this.registerAlias("decimal[]", BigDecimal[].class); this.registerAlias("bigdecimal[]", BigDecimal[].class); this.registerAlias("biginteger[]", BigInteger[].class); this.registerAlias("object[]", Object[].class); this.registerAlias("map", Map.class); this.registerAlias("hashmap", HashMap.class); this.registerAlias("list", List.class); this.registerAlias("arraylist", ArrayList.class); this.registerAlias("collection", Collection.class); this.registerAlias("iterator", Iterator.class); this.registerAlias("ResultSet", ResultSet.class); } public void registerAliases(String packageName) { this.registerAliases(packageName, Object.class); } //slightly }
In fact, there is a HashMap in typealias registry, and many aliases are registered in the builder of typealias registry to this HashMap. OK, now we just create an XMLConfigBuilder, in which we create a Configuration object. Next, we will analyze mybatis-config.xml into the corresponding properties in Configuration, and also It is the parser.parse() method:
XMLConfigBuilder
1 public Configuration parse() { 2 if (parsed) { 3 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 4 } 5 parsed = true; 6 // Analytical configuration 7 parseConfiguration(parser.evalNode("/configuration")); 8 return configuration; 9 }
Let's look at line 7 and notice an xpath expression - / configuration. This expression represents the < configuration / > tag of MyBatis. Select this tag here and pass it to the parseConfiguration method. Let's keep going.
private void parseConfiguration(XNode root) { try { // analysis properties To configure propertiesElement(root.evalNode("properties")); // analysis settings Configure and convert it to Properties object Properties settings = settingsAsProperties(root.evalNode("settings")); // Load vfs loadCustomVfs(settings); // analysis typeAliases To configure typeAliasesElement(root.evalNode("typeAliases")); // analysis plugins To configure pluginElement(root.evalNode("plugins")); // analysis objectFactory To configure objectFactoryElement(root.evalNode("objectFactory")); // analysis objectWrapperFactory To configure objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); // analysis reflectorFactory To configure reflectorFactoryElement(root.evalNode("reflectorFactory")); // settings The information in is set to Configuration In object settingsElement(settings); // analysis environments To configure environmentsElement(root.evalNode("environments")); // analysis databaseIdProvider,Get and set databaseId reach Configuration object databaseIdProviderElement(root.evalNode("databaseIdProvider")); // analysis typeHandlers To configure typeHandlerElement(root.evalNode("typeHandlers")); // analysis mappers To configure mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
Resolving properties configuration
Let's take a look at the configuration of the properties node. As follows:
<properties resource="db.properties"> <property name="username" value="root"/> <property name="password" value="123456"/> </properties>
I have configured a resource property for the properties node, as well as two child nodes. Then let's look at the logic of propertiesElement
private void propertiesElement(XNode context) throws Exception { if (context != null) { // analysis propertis And convert the contents of these nodes to attribute objects Properties Properties defaults = context.getChildrenAsProperties(); // Obtain propertis Node resource and url Attribute value String resource = context.getStringAttribute("resource"); String url = context.getStringAttribute("url"); // If neither is empty, an exception will be 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) { // Load and parse property files from the file system defaults.putAll(Resources.getResourceAsProperties(resource)); } else if (url != null) { // adopt url Load and parse property file defaults.putAll(Resources.getUrlAsProperties(url)); } Properties vars = configuration.getVariables(); if (vars != null) { defaults.putAll(vars); } parser.setVariables(defaults); // Set property value to configuration in configuration.setVariables(defaults); } } public Properties getChildrenAsProperties() { //Create a Properties object Properties properties = new Properties(); // Get and traverse child nodes for (XNode child : getChildren()) { // Obtain property Nodal name and value attribute String name = child.getStringAttribute("name"); String value = child.getStringAttribute("value"); if (name != null && value != null) { // Set property to property object properties.setProperty(name, value); } } return properties; } // -☆- XNode public List<XNode> getChildren() { List<XNode> children = new ArrayList<XNode>(); // Get the list of child nodes NodeList nodeList = node.getChildNodes(); if (nodeList != null) { for (int i = 0, n = nodeList.getLength(); i < n; i++) { Node node = nodeList.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { children.add(new XNode(xpathParser, node, variables)); } } } return children; }
There are three steps to resolve properties:
- Resolve the child nodes of the properties node and set the resolution results to the properties object.
- Read the property configuration from the file system or over the network, depending on whether the resource and url of the properties node are empty.
- Set the resolved property object to the XPathParser and Configuration objects.
It should be noted that the propertiesElement method first resolves the content of the child nodes of the properties node, then reads the property configuration from the file system or the network, and puts all the properties and property values into the defaults property object. This will lead to the problem of property overwrite with the same name, that is, the property and property value read from the file system or the network will overwrite the property and property value with the same name in the properties sub node.
Analyze settings configuration
The resolution process of settings node
Let's take a look at a simple configuration of settings, as follows:
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="autoMappingBehavior" value="PARTIAL"/> </settings>
Let's take a look at settings as properties
private Properties settingsAsProperties(XNode context) { if (context == null) { return new Properties(); } // Obtain settings The content in the child node, parsed into Properties,getChildrenAsProperties Method has been analyzed before Properties props = context.getChildrenAsProperties(); // Establish Configuration Class's meta information object MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory); for (Object key : props.keySet()) { // Testing Configuration Whether there are related properties in, if not, an exception will be thrown. 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; }
Set settings to Configuration
Next let's look at the process of setting the settings Configuration to the Configuration object. As follows:
private void settingsElement(Properties props) throws Exception { // Set up autoMappingBehavior Property, default is PARTIAL configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL"))); configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE"))); // Set up cacheEnabled Property, default is true configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true)); // Omit some codes // Resolving the default enumeration processor Class<? extends TypeHandler> typeHandler = (Class<? extends TypeHandler>)resolveClass(props.getProperty("defaultEnumTypeHandler")); // Set default enumeration processor configuration.setDefaultEnumTypeHandler(typeHandler); configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false)); configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true)); // Omit some codes }
The above code handles calling the setter method of Configuration
Parsing typeAliases configuration
In MyBatis, we can define an alias for some of the classes we write ourselves. In this way, when using, we only need to enter the alias, without writing out the fully qualified class name. In MyBatis, we have two ways to configure aliases. The first is to configure only the package name, let MyBatis scan the types in the package, and get the corresponding aliases according to the types.
<typeAliases>
<package name="com.mybatis.model"/>
</typeAliases>
The second way is to explicitly configure aliases for a type manually. The configuration of this method is as follows:
<typeAliases> <typeAlias alias="employe" type="com.mybatis.model.Employe" /> <typeAlias type="com.mybatis.model.User" /> </typeAliases>
Let's take a look at how two different alias configurations are resolved. The code is as follows:
XMLConfigBuilder
private void typeAliasesElement(XNode parent) { if (parent != null) { for (XNode child : parent.getChildren()) { // Resolves alias and type mappings from the specified package if ("package".equals(child.getName())) { String typeAliasPackage = child.getStringAttribute("name"); configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage); // from typeAlias Mapping of resolving aliases and types in nodes } else { // Obtain alias and type Attribute values alias Not required, can be empty String alias = child.getStringAttribute("alias"); String type = child.getStringAttribute("type"); try { // Load type Corresponding type Class<?> clazz = Resources.classForName(type); // Register alias to type mapping 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); } } } } }
We can see that whether the name of the child node is package is used to judge through package scanning and manual registration.
Resolve and register aliases from the typeAlias node
In alias configuration, the type attribute must be configured, while the alias attribute is not.
private final Map<String, Class<?>> TYPE_ALIASES = new HashMap<String, Class<?>>(); public void registerAlias(Class<?> type) { // Get the short name of the full path class name String alias = type.getSimpleName(); Alias aliasAnnotation = type.getAnnotation(Alias.class); if (aliasAnnotation != null) { // Take alias from annotation alias = aliasAnnotation.value(); } // Call overload method to register alias and type mapping registerAlias(alias, type); } public void registerAlias(String alias, Class<?> value) { if (alias == null) { throw new TypeException("The parameter alias cannot be null"); } // Convert alias to lowercase String key = alias.toLowerCase(Locale.ENGLISH); /* * If there is a type mapping in type ﹣ aliases, it is judged whether the current type is consistent with the type in the mapping. * Throw an exception if it is inconsistent. An alias is not allowed to correspond to two types. */ if (TYPE_ALIASES.containsKey(key) && TYPE_ALIASES.get(key) != null && !TYPE_ALIASES.get(key).equals(value)) { throw new TypeException( "The alias '" + alias + "' is already mapped to the value '" + TYPE_ALIASES.get(key).getName() + "'."); } // Cache alias to type mapping TYPE_ALIASES.put(key, value); }
If the user explicitly configures the Alias attribute, MyBatis will use the lowercase form of the class name as the Alias. For example, the fully qualified class name com.mybatis.model.User is Alias user. If there is @ Alias annotation in the class, the value is taken from the annotation as the Alias.
Resolves and registers aliases from the specified package
public void registerAliases(String packageName) { registerAliases(packageName, Object.class); } public void registerAliases(String packageName, Class<?> superType) { ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>(); /* * Find the class whose parent class under the package is Object.class. * After the lookup is completed, the lookup results will be cached in the internal collection of resolveutil. */ resolverUtil.find(new ResolverUtil.IsA(superType), packageName); // Get search results Set<Class<? extends Class<?>>> typeSet = resolverUtil.getClasses(); for (Class<?> type : typeSet) { // Ignore anonymous class, interface, inner class if (!type.isAnonymousClass() && !type.isInterface() && !type.isMemberClass()) { // Register alias for type registerAlias(type); } } }
There are two main steps:
- Find all classes under the specified package
- Traverse the found type collection and register aliases for each type
Let's look at all the classes under the specified package.
private Set<Class<? extends T>> matches = new HashSet(); public ResolverUtil<T> find(ResolverUtil.Test test, String packageName) { //Convert package name to file path String path = this.getPackagePath(packageName); try { //adopt VFS(Virtual file system) gets the pathnames of all files under the specified package.such as com/mybatis/model/Employe.class List<String> children = VFS.getInstance().list(path); Iterator i$ = children.iterator(); while(i$.hasNext()) { String child = (String)i$.next(); //with.class The ending file is added to the Set Collection if (child.endsWith(".class")) { this.addIfMatching(test, child); } } } catch (IOException var7) { log.error("Could not read package: " + packageName, var7); } return this; } protected String getPackagePath(String packageName) { //Convert package name to file path return packageName == null ? null : packageName.replace('.', '/'); } protected void addIfMatching(ResolverUtil.Test test, String fqn) { try { //Convert the pathname to the fully qualified class name, and load the class name through the class loader, such as com.mybatis.model.Employe.class String externalName = fqn.substring(0, fqn.indexOf(46)).replace('/', '.'); ClassLoader loader = this.getClassLoader(); if (log.isDebugEnabled()) { log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); } Class<?> type = loader.loadClass(externalName); if (test.matches(type)) { //Add to matches Collection this.matches.add(type); } } catch (Throwable var6) { log.warn("Could not examine class '" + fqn + "'" + " due to a " + var6.getClass().getName() + " with message: " + var6.getMessage()); } }
The main steps are as follows:
- Obtain the pathnames of all files under the specified package through VFS (virtual file system), such as COM / mybatis / model / employee.class.
- Filter filenames ending in. class
- Convert the pathname to the fully qualified class name, and load the class name through the class loader
- Match the type and put it into the internal collection if the matching rule is met
Here we should note that when we analyzed the creation of Configuration objects, we have registered many aliases by default. You can go back to the beginning of this article.
Parsing plugins configuration
The plug-in is an extension mechanism provided by MyBatis. Through the plug-in mechanism, we can do some custom operations at some points in the SQL execution process. This is a metaphor for paging plug-ins. Before SQL execution, we will dynamically splice statements. We will talk about the plug-in mechanism separately later. First, we will understand the plug-in configuration. As follows:
<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> </plugin> </plugins>
The analysis process is as follows:
private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); // Get configuration information Properties properties = child.getChildrenAsProperties(); // Parse the type of interceptor and create the interceptor Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); // set a property interceptorInstance.setProperties(properties); // Add interceptor to Configuration in configuration.addInterceptor(interceptorInstance); } } }
First, get the Configuration, then parse the interceptor type and instantiate the interceptor. Finally, set the properties to the interceptor and add the interceptor to the Configuration.
private void pluginElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { String interceptor = child.getStringAttribute("interceptor"); // Get configuration information Properties properties = child.getChildrenAsProperties(); // Analyze the type of interceptor and instantiate the interceptor Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance(); // set a property interceptorInstance.setProperties(properties); // Add interceptor to Configuration in configuration.addInterceptor(interceptorInstance); } } } public void addInterceptor(Interceptor interceptor) { //Add to Configuration Of interceptorChain Attribute this.interceptorChain.addInterceptor(interceptor); }
Let's take a look at interceptor chain.
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList(); public InterceptorChain() { } public Object pluginAll(Object target) { Interceptor interceptor; for(Iterator i$ = this.interceptors.iterator(); i$.hasNext(); target = interceptor.plugin(target)) { interceptor = (Interceptor)i$.next(); } return target; } public void addInterceptor(Interceptor interceptor) { this.interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(this.interceptors); } }
It's actually a set of interceptors. We'll talk about the principle of plug-ins later.
Analyzing environment configuration
In MyBatis, the transaction manager and data sources are configured in environments. Their configuration is roughly as follows:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</dataSource>
</environment>
</environments>
Let's take a look at the environmentslelement method
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { // Obtain default attribute environment = context.getStringAttribute("default"); } for (XNode child : context.getChildren()) { // Obtain id attribute String id = child.getStringAttribute("id"); /* * Detect the id of the current environment node and the attribute default of its parent environment * If the content is consistent, true will be returned; otherwise, false will be returned. * Set the subelement whose default attribute value is equal to the id attribute value of the subelement environment to the currently used environment object */ if (isSpecifiedEnvironment(id)) { // take environment Medium transactionManager Label to TransactionFactory object TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // take environment Medium dataSource Label to DataSourceFactory object DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); // Establish DataSource object DataSource dataSource = dsFactory.getDataSource(); Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // structure Environment Object and set to configuration in configuration.setEnvironment(environmentBuilder.build()); } } } }
Take a look at the acquisition of TransactionFactory and DataSourceFactory
private TransactionFactory transactionManagerElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); Properties props = context.getChildrenAsProperties(); //Get by alias Class,Instantiation TransactionFactory factory = (TransactionFactory)this.resolveClass(type).newInstance(); factory.setProperties(props); return factory; } else { throw new BuilderException("Environment declaration requires a TransactionFactory."); } } private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); //Get by alias Class,Instantiation Properties props = context.getChildrenAsProperties(); DataSourceFactory factory = (DataSourceFactory)this.resolveClass(type).newInstance(); factory.setProperties(props); return factory; } else { throw new BuilderException("Environment declaration requires a DataSourceFactory."); } }
< transactionmanager type = "JDBC" / > there are two configurations of "JDBC" and "MANAGED" in the type. In the alias registered by default in our previous Configuration, there are two corresponding transactionfactories: JdbcTransactionFactory.class and ManagedTransactionFactory.class.
There are three configurations of "JNDI", "POOLED" and "UNPOOLED" in < datasource type = "POOLED" > the default registered alias includes three datasourcefactories: JndiDataSourceFactory.class, PooledDataSourceFactory.class and UnpooledDataSourceFactory.class.
In our environment configuration, if transactionManager type="JDBC" and dataSource type="POOLED", the generated transactionManager is JdbcTransactionFactory and DataSourceFactory is pooledatasourcefactory.
Let's take a look at pooledatasourcefactory and UnpooledDataSourceFactory
public class UnpooledDataSourceFactory implements DataSourceFactory { private static final String DRIVER_PROPERTY_PREFIX = "driver."; private static final int DRIVER_PROPERTY_PREFIX_LENGTH = "driver.".length(); //Establish UnpooledDataSource Example protected DataSource dataSource = new UnpooledDataSource(); public DataSource getDataSource() { return this.dataSource; } //slightly } //inherit UnpooledDataSourceFactory public class PooledDataSourceFactory extends UnpooledDataSourceFactory { public PooledDataSourceFactory() { //Establish PooledDataSource Example this.dataSource = new PooledDataSource(); } }
We found that the dataSource created by UnpooledDataSourceFactory is UnpooledDataSource, and the dataSource created by PooledDataSourceFactory is PooledDataSource.
Parsing mappers configuration
The mapperElement method will convert the elements in the mapper tag into MapperProxyFactory generated proxy classes and bind to the mapper.xml file, which will be explained in the next article.
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { 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()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); 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."); } } } } }
Create DefaultSqlSessionFactory
So far, we have gone through the important steps in the parse method of XMLConfigBuilder, and then we return a complete Configuration object. Finally, we create a SqlSessionFactory instance DefaultSqlSessionFactory through the overload method of SqlSessionFactoryBuilder's build. Let's take a look at it.
public SqlSessionFactory build(Configuration config) { //Establish DefaultSqlSessionFactory Example return new DefaultSqlSessionFactory(config); } public class DefaultSqlSessionFactory implements SqlSessionFactory { private final Configuration configuration; //Just will configuration Set as its property public DefaultSqlSessionFactory(Configuration configuration) { this.configuration = configuration; } //slightly }