title: Mybatis source code analysis
date: 2021-02-17 11:21:28
tags: mybatis
description: Mybatis source code learning
(overall architecture diagram)
(source package frame composition)
1. Configuration file parsing process
Build SqlSessionFactory from configuration file
String resource = "mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
First, we will use the tool class Resources provided by MyBatis to load the configuration file and get an input stream. Then build the SqlSessionFactory object through the build method of the SqlSessionFactory builder object. The build method here is the entry method for us to analyze the configuration file parsing process
The build method creates a configuration file parser
public SqlSessionFactory build(InputStream inputStream,String environment,Properties properties){ //Create file parser XMLConfigBuilder praser = new XMLConfigBuilder(inputStream, environment, properties); //Call the parse method to parse the Configuration file and generate the Configuration object return build(parser.prase()); }
The configuration file is parsed through XMLConfigBuilder. The following is the prase method of XMLCnfigBuilder:
public Configuration parse(){ ... //Resolution configuration parseConfiguration(parser.evalNode("/configuration")); return configuration; }
The xpath expression / configuration represents the node in the configuration file. Here, select the node through xpath and pass it to the parseConfiguration method:
private void parseConfiguration(XNode root){ //Configure properties configuration propertiesElement(root.evalNode("properties")); //Resolve the settings configuration and convert it to a Properties object Properties settings = settingAsProperties(root.evalNode("settings")); //Load vfs loadCustomVfs(settings); //Parsing typeAlases configuration typeAliasesElement(root.evalNode("typeAliases")); //Parsing plugins configuration pluginElement(root.evalNode("plugins")); ... }
1.1 parsing the properties node
properties node configuration
<properties resource="jdbc.properties"> <property name="jdbc.username" value="coolblog"/> <property name="hello" value="world"/> </properties>
Node resolution mainly includes three steps. One is to resolve the child nodes of the node and set the resolution results to the Properties object. The second is to read the attribute Configuration from the file system or through the network, depending on whether the resource and url of the node are empty. The last step is to set the Properties object containing property information into XPathParser and Configuration.
1.2 parsing settings node
Configuration node
<settings> <setting name="cacheEnabled" value="true"/> <setting name="lazyLoadingEnabled" value="true"/> <setting name="autoMppingBehavior" value="PARTAL"/> </settings>
Parsing process:
private Properties settingsAsProperties(XNode context){ //Get the content in the settings child node Properties props = context.getChildrenAsProperties(); //Create a meta information object for the Configuration class MetaClass metaConfig = MetaClass.forClass(Configuration.class,localReflectorFactory); for(Object key : props.keySet()){ //Monitor whether there are relevant properties in the Configuration } }
- Parse the contents of the settings child node and convert the parsing results into Properties objects
- Create a meta information object for Configuration
- Check whether the setter method of a property exists in the Configuration through MetaClass. If it does not exist, throw an exception
- If it passes the detection of MetaClass, the Properties object is returned and the method logic ends
1.3 set settings to Configuration
After the content of the settings node is parsed, it can be stored in the Configuration object. The code mainly calls the setter method of Configuration
1.4 parsing typeAliases nodes
The way to configure the package name allows Mybatis to scan the types in the package and get the corresponding aliases according to the types, which can be used in conjunction with Alias annotations, that is, configure aliases for a class through annotations
<typeAliases> <package name="xyz.coolblog.chapter2.model1"/> <package name="xyz.coolblog.chapter2.model2"/> </typeAliases>
Manually, explicitly configure aliases for a type
<typeAliases> <typeAlias alias="article" type="xyz.coolblog.chapter2.model.Article"/> <typeAlias alias="author" type="xyz.coolblog.chapter2.model.Author"/> </typeAliases>
-
Resolve and register aliases from nodes
The type attribute must be configured, while the alias attribute is not required. If the user does not configure the alias attribute, mybatis needs to generate an alias for the target type (using the lowercase form of the class name as the alias). If the alias is empty, the task of registering the alias is handed over to the registeralias (class <? >) method. If it is not empty, the alias is registered by registeralias (string, class <? >)
-
Resolves and registers the alias from the specified package
Find all classes under the specified package, traverse the found type collection, and register aliases for each type
- Obtain the pathnames of all files under the specified package through VFS (virtual file system),
- For example, xyz/coolblog/model/Article.class
- Filter file names ending in. class
- Convert the pathname to a fully qualified class name and load the class name through the class loader
- Match the type. If it meets the matching rules, it will be put into the internal collection
In short, a Map < string, class <? > > is created, Parse the configuration file of mybatis, take the value of alias element as the key of Map, take the class of class name corresponding to type element obtained through reflection mechanism as the value of Map, and obtain the real class through alias alias in real use.
1.5 parsing plugins node
Implementing a plug-in needs to be simpler than. First, you need to make the plug-in class implement the Interceptor interface. Then add @ Intercepts and @ Signature annotations on the plug-in class to specify the target method to be intercepted
<plugins> <plugin interceptor="xyz.coolblog.mybatis.ExamplePlugin"> <property name="key" value="value"/> </plugin> </plugins>
Resolution process of plug-in: obtain the Configuration, then resolve the interceptor type, instantiate the interceptor, finally set the properties in the interceptor, and add the interceptor to the Configuration
1.6 parsing the environments node
In mybatis, the transaction manager and data source are configured in the environments node
<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>
Parsing process: obtain the default attribute of environments. The id of the environment node should be consistent with the default attribute of the parent node environments. Parse the transactionManager node, parse the datasource node, create the datasource object, create the environment object and set it in the configuration.
1.7 parsing typeHandlers node
When storing or reading data to the database, we need to convert the database field type to Java type. The following is the configuration method of type processor:
<!--Automatic scanning--> <!--When automatically scanning registered type processors, use@MappedTypes and@MappedJdbcTypes Annotation configuration javaType and jdbcType> <typeHandlers> <package name="xyz.coolblog.handlers"/> </typeHandlers> <!--Manual configuration--> <typeHandlers> <typeHandler jdbcType="TINYINT" javaType="xyz.coolblog.constant.ArticleTypeEnum" handler="xyz.coolblog.mybatis.ArticleTypeHandler"/> </typeHandlers>
Parse the xml and call different types of processor registration methods
-
When javatypeclass= null&&jdbcType != When null, the values of javaType and jdbctype attributes are explicitly configured, and register(Class,jdbcType,Class) is called for registration, that is, the type and processor are mapped
private void register(Type javaType, JdbcType jdbcType,TypeHandler<?> handler){ if(javaType != null){ //JDBC type to TypeHandler mapping Map<JdbcType, TypeHandler<?>> map = TYPE_HANDLER_MAP.get(javaType); if(map==null||map == NULL_TYPE_HANDLER_MAP){ map = new HashMap<JdbcType, TypeHandler<?>>(); //Store the mapping from javaType to map < jdbctype, typehandler > TYPE_HANDLER_MAP.put(javaType,map); } map.put(jdbcType,handler); } //Store all typehandlers ALL_TYPE_HANDLERS_MAP.put(handler.getClass(),handler); }
-
When javaType= Null & & JdbcType = = null, that is, only the value of javaType is set. Register (class <? > javatypeclass, class <? > typehandlerclass) is called to try to obtain the value of JdbcType from the annotation
-
When javaType = = null & & jdbctype= When null, that is, only the value of javaType is set. Register (class <? > typehandlerclass) is called to try to resolve the value of javaType
2. Mapping file parsing process
Mybatis will parse the mapping file during the process of parsing the configuration file, and the parsing logic is encapsulated in the mapperElement method
//XMLConfigBuilder private void mapperElement(XNode parent) throws Exception{ if(parent!=null){ for(XNode child:parent.getChildren()){ if("package".equals(child.getName())){ //Get the name attribute in the < package > node String mapperPackage = child.getStringAttribute("name"); //Find the mapper interface from the package, and resolve the mapping configuration according to the mapper interface configuration.addMappers(mapperPackage); }else{ //Get properties such as resource/url/class //If the resource is not empty, the configuration is loaded from the specified path ... } } } }
The main logic of the above code is to traverse the child nodes of mappers and judge how to load the mapping file or mapping information according to the node attribute value. Here, the content configured in the annotation is called mapping information, and the configuration with XML as the carrier is called mapping file. In MyBatis, there are four ways to load mapping files or mapping information. The first is to load the mapping file from the file system; The second is to load the mapping file through the URL; The third is to load mapping information through the mapper interface. Mapping information can be configured in annotations or mapping files. The last method is to obtain all classes under a package by package scanning, and use the third method to resolve the mapping information for each class.
Let's analyze the parsing process of the mapping file. First look at the mapping file parsing entry:
//XMLMapperBuilder public void parse(){ //Monitor whether the mapping file has been parsed if(!configuration.isResourceLoaded(resource)){ //Resolve mapper node configurationElement(parser.evalNode("/mapper")); //Add a resource path to the resolved resource collection configuration.addLoadedResource(resource); //Binding Mapper interface through namespace bindMapperForNamespace(); //Processing unresolved nodes parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } }
The mapping file parsing entry logic contains three core operations, as follows:
- Resolve mapper node
- Binding Mapper interface through namespace
- Processing unresolved nodes
2.1 parsing mapping files
<mapper namespace="xyz.coolblog.dao.AuthorDao"> <cache/> <resultMap id="authorResult" type="Author"> <id property="id" colunn="id"/> <result property="name" column="name"/> <!--...--> </resultMap> <sql id="table"> author </sql> <select id="findOne" resultMap="authorResult"> SELECT id,name,age,sex,email FROM <include refid="table"/> WHERE id=#{id} </select> </mapper>
The parsing logic of each node in the above configuration is encapsulated in corresponding methods, which are uniformly called by the configurationElement method of XMLMapperBuilder class. The logic of this method is as follows:
private void configurationElement(XNode context){ try{ //Get mapper namespace String namespace = context.getStringAttribute("namespace"); //exception handling } //Set namespace to builder assistant builderAssistant.setCurrentNamespace(namespace); //Parsing cache ref nodes cacheRefElement(context.evalNode("cache-ref")); //Parsing cache nodes cacheElement(context.evalNode("chche")); //Resolve resultMap node resultMapElements(context.evalNodes("/mapper/resultMap")); //Parsing sql nodes sqlElement(context.evalNodes("/mapper/sql")); //Resolve select, delete and other nodes buildStatementFromContext(context.evalNodes("select|insert|update|delete")); }
2.2 parsing cache nodes
mybatis provides two-level cache. The first level cache is the SqlSession level, which is enabled by default. The second level cache is configured in the mapping file, and the user needs to display the configuration to enable it
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
The cache created according to the above configuration has the following characteristics:
- Eliminate cache entries by first in first out policy
- The cache has a capacity of 512 object references
- The cache is refreshed every 60 seconds
- The object returned by the cache is write safe, that is, modifying the object externally will not affect the internal storage object of the cache
Cache configuration resolution:
2.3 parsing cache ref nodes
2.4 parsing resultMap node
<!-- mybatis-config.xml in --> <!--User class--> <typeAlias type="com.someapp.model.User" alias="User"/> <!-- SQL mapping XML in --> <select id="selectUsers" resultType="User"> select id, username, hashedPassword from some_table where id = #{id} </select> <!--Display configuration resultMap--> <resultMap id="userResultMap" type="User"> <id property="id" column="user_id" /> <result property="username" column="user_name"/> <result property="password" column="hashed_password"/> </resultMap>
-
Get various properties of the resultMap node
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); // Get id and type attributes String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); // Get extensions and autoMapping String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); // Resolve the type corresponding to the type attribute Class<?> typeClass = resolveClass(type); Discriminator discriminator = null; List<ResultMapping> resultMappings = new ArrayList<ResultMapping>(); resultMappings.addAll(additionalResultMappings);
-
Traverse the child nodes of resultMap and execute the corresponding parsing logic according to the child node name
// Get and traverse the list of child nodes of < resultmap > List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { if ("constructor".equals(resultChild.getName())) { // Parse the constructor node and generate the corresponding ResultMapping processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { // Resolve discriminator discriminator node discriminator = processDiscriminatorElement( resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<ResultFlag>(); if ("id".equals(resultChild.getName())) { // Add ID to flags collection flags.add(ResultFlag.ID); } // Resolve the id and property nodes and generate the corresponding ResultMapping resultMappings.add(buildResultMappingFromContext( resultChild, typeClass, flags)); } } ResultMapResolver resultMapResolver = new ResultMapResolver( builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
-
Building ResultMap objects
-
If an exception occurs during the build, add resultMapResolver to the
incompleteResultMaps collectiontry { // Build the ResultMap object according to the information obtained earlier return resultMapResolver.resolve(); } catch (IncompleteElementException e) { /* * If an IncompleteElementException exception occurs, * Here, add the resultMapResolver to the incompleteResultMaps collection */ configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
-
Resolve id and result nodes
-
Resolution process of resultMap attribute
This attribute exists in and nodes -
<!--adopt resultMap Property references other resultMap node--> <resultMap id="articleResult" type="Article"> <id property="id" column="id"/> <result property="title" column="article_title"/> <association property="article_author" column="article_author_id" resultMap="authorResult"/> </resultMap> <resultMap id="authorResult" type="Author"> <id property="id" column="author_id"/> <result property="name" column="author_name"/> </resultMap>
-
<!--take resultMap Nested configuration--> <resultMap id="articleResult" type="Article"> <id property="id" column="id"/> <result property="title" column="article_title"/> <association property="article_author" javaType="Author"> <id property="id" column="author_id"/> <result property="name" column="author_name"/> </association> </resultMap>
-