Mybatis source code analysis

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
    }
}
  1. Parse the contents of the settings child node and convert the parsing results into Properties objects
  2. Create a meta information object for Configuration
  3. Check whether the setter method of a property exists in the Configuration through MetaClass. If it does not exist, throw an exception
  4. 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

    1. Obtain the pathnames of all files under the specified package through VFS (virtual file system),
    2. For example, xyz/coolblog/model/Article.class
    3. Filter file names ending in. class
    4. Convert the pathname to a fully qualified class name and load the class name through the class loader
    5. 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:

  1. Resolve mapper node
  2. Binding Mapper interface through namespace
  3. 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:

  1. Eliminate cache entries by first in first out policy
  2. The cache has a capacity of 512 object references
  3. The cache is refreshed every 60 seconds
  4. 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>
  1. 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);
    
  2. 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);
    
  3. Building ResultMap objects

  4. If an exception occurs during the build, add resultMapResolver to the
    incompleteResultMaps collection

    try { 
            // 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>
      

Keywords: Java

Added by kaeRock on Tue, 14 Sep 2021 05:25:56 +0300