Mybatis global configuration file <mappers> tag analysis

write in front

In this article we analyze the process of parsing the global configuration file, in which "5.1.10: mappers" analyzes the parsing<mappers/>The process of parsing the corresponding mapper xml when the tag is parsed will be parsed into an XMLMapperBuilder object, but the parsing process has not been analyzed in detail. Let’s take a look at this article. As a supplement, the work done is actuallyLoad mapper xml mapping configuration file.
If you want to learn systematically, you can refer to this article, important! ! ! .

1: Entrance

The source code is as follows:

org . apache . ibatis . builder . xml . XMLConfigBuilder #parseConfiguration
 private  void  parseConfiguration ( XNode root )  { 
  try  { 
    . . . snip . . . 
    // <202107161610> 
    mapperElement ( root . evalNode ( "mappers" ) ) ; 
  }  catch  ( Exception e )  { 
    throw  new BuilderException ( "Error parsing SQL Mapper Configuration. Cause: "  + e , e ) ; 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

<202107161610>The source code is as follows:

org . apache . ibatis . builder . xml . XMLConfigBuilder #mapperElement
 private  void  mapperElement ( XNode parent )  throws  Exception  { 
  if ( parent !=  null )  { 
    for  ( XNode child : parent . getChildren ( ) )  { 
      // There are packages, such as configuration :
       /*
	  <mappers>
        <package name="yudaosourcecode.mybatis.mapper"/>
      </mappers>
	  */ 
      if  ( "package" . equals ( child . getName ( ) ) )  { 
        // Get the value of name. Namely package name 
        String mapperPackage = child . getStringAttribute ( "name" ) ; 
        // <202108101735> 
        configuration . addMappers ( mapperPackage ) ; 
      }  else  { 
        // Get resource, such as <mapper resource="mybatis/mapper/TestTypehandlerMapper.xml"/ > 
        String resource = child .getStringAttribute ( "resource" ) ; 
        // Get url 
        String url = child . getStringAttribute ( "url" ) ; 
        // Get class 
        String mapperClass = child . getStringAttribute ( "class" ) ; 
        // Prioritize resource processing, which is class path in this case resource 
        if  ( resource !=  null  && url ==  null  && mapperClass ==  null )  { 
          ErrorContext . instance () . resource ( resource ) ; 
          // get resource 
          InputStream inputStream =  Resources . getResourceAsStream ( resource ) ; 
          // create XMLMapperBuilder 
          // <202107161617> 
          XMLMapperBuilder mapperParser =  new  XMLMapperBuilder ( inputStream , configuration , resource , configuration . getSqlFragments ( ) ) ; 
          / / Execute parsing. After parsing, you will know which interface class corresponds to mapper xml is
           // <202107161724> 
          mapperParser . parse ( ) ; 
        }  else  if  ( resource ==  null  && url !=  null  && mapperClass ==  null )  {  // handle url case 
          ErrorContext . instance ( ) . resource ( url ) ; 
          // get resource 
          InputStream inputStream according to url =  Resources . getUrlAsStream ( url ) ; 
          // create XMLMapperBuilder
          XMLMapperBuilder mapperParser =  new  XMLMapperBuilder ( inputStream , configuration , url , configuration . getSqlFragments ( ) ) ; 
          // perform parsing 
          mapperParser . parse ( ) ; 
        }  else  if  ( resource ==  null  && url ==  null  && mapperClass !=  null )  {  / / Directly specified class // Get the class 
     	  Class of the mapped mapper interface
          < ? > mapperInterface=  Resources . classForName ( mapperClass ) ; 
          // add mapping 
          configuration . addMapper ( mapperInterface ) ; 
        }  else  { 
          throw  new  BuilderException ( "A mapper element may only specify a url, resource or class, but not more than one." ) ; 
        } 
      } 
    } 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

<202107161617>The source code of the constructor function is as follows:

public  XMLMapperBuilder ( InputStream inputStream ,  Configuration configuration ,  String resource ,  Map < String ,  XNode > sqlFragments )  { 
  // XPathParser: is a tool for parsing xml based on xpath, in the parsing package of mybatis, it belongs to a function of parsing module 
  // configuration .getVariables(): Information configured through the <properties/> tag in the global configuration file 
  // <202107161626> 
  this ( new  XPathParser ( inputStream ,  true , configuration . getVariables ( ) ,  new XMLMapperEntityResolver ( ) ) , 
      configuration , resource , sqlFragments ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

<202107161626>The source code is as follows:

/*
parser: a tool class for parsing xml based on xpath
configuration: The configuration class obtained by parsing the global config configuration file
resource: location of mapper xml
sqlFragments: a collection of sql statement segments that store the configuration of the <sql> tag
*/ 
private  XMLMapperBuilder ( XPathParser parser ,  Configuration configuration ,  String resource ,  Map < String ,  XNode > sqlFragments )  { 
  super ( configuration ) ; 
  // Create MapperBuilderAssistant object 
  this . builderAssistant =  new  MapperBuilderAssistant ( configuration , resource ) ; 
  this . parser = parser; 
  this . sqlFragments = sqlFragments ; 
  this . resource = resource ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

<202107161724>The place is to perform parsing, for specific reference2: XMLMapperBuilder#parse.<202108101735>is configured in the global configuration file through<package>Directly configure the package path to be scanned, which may be configured as follows:

< mappers > 
    < package  name = " yudaosourcecode.mybatis.annotation " /> 
</ mappers >
  • 1
  • 2
  • 3

This configuration will not only parse to mapper xml, but also parse to mapper interface configured using annotationsMapper xml is not needed at this point, this article will analyze the former, and you can refer to this article for the latter .

2: XMLMapperBuilder#parse

The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #parse
 public  void  parse ( )  { 
  // <202107161747> 
  if  ( ! configuration . isResourceLoaded ( resource ) )  { 
    // <202107161755> 
    // parse the <mapper/> node 
    configurationElement ( parser . evalNode ( "/mapper" ) ) ; 
    // mark the resource as loaded 
    /*
	org.apache.ibatis.session.Configuration#addLoadedResource
	public void addLoadedResource(String resource) {
	   // protected final Set<String> loadedResources = new HashSet<String>();
	   loadedResources.add(resource);
    }
	*/ 
    configuration . addLoadedResource ( resource ) ; 
    // The jdk dynamic proxy implementation class that binds the namespace to the mapper interface 
   	// <202107301547> 
    bindMapperForNamespace ( ) ; 
  } 
  // Note that the following pending means that there is a parsing in the previous parsing To the half-failed node, it may be because some dependent nodes have not been resolved to cause 
  // so repeat the analysis here again 
  // parsing the pending resultMap node 
  parsePendingResultMaps ( ) ; 
  // parsing the pending cache-ref node 
  parsePendingChacheRefs ( ) ; 
  // parsing Pending statement node 
  parsePendingStatements ( ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

<202107161747>The point is to judge whether the mapp xml resource has been loaded. The source code is as follows:

org . apache . ibatis . session . Configuration #isResourceLoaded
 public  boolean  isResourceLoaded ( String resource )  { 
 // protected final Set<String> loadedResources = new HashSet<String>(); 
 // Store the location corresponding to the loaded resources through the set collection info 
 return loadedResources . contains ( resource ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

<202107161755>The place is to parse the mapper node, for specific reference2.1: mapper node analysis,<202107301547>The place is to bind the corresponding namespace of mapper xml to the specific jdk dynamic proxy of the mapper interface. The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #bindMapperForNamespace
 private  void  bindMapperForNamespace ( )  { 
  // Get the namespace, which is actually the interface name of the mapper, which is normally configured like this 
  Stringnamespace  = builderAssistant . getCurrentNamespace ( ) ; 
  if  ( namespace ! =  null )  { 
    Class < ? > boundType =  null ; 
    try  {
       // Get the bound class type by reflection 
      boundType =  Resources . classForName ( namespace ) ; 
    }  catch  ( ClassNotFoundException e )  { 
    } 
    if  ( boundType !=  null )  { 
      // if no current mapper information exists 
      // protected MapperRegistry mapperRegistry = new MapperRegistry(this); 
      if  ( ! configuration . hasMapper ( boundType ) )  { 
        // prevent repeated loading 
        // protected final Set<String> loadedResources = new HashSet<String>(); 
        configuration.addLoadedResource ( //"namespace:"  + namespace ) ; 
        // Set the type of the map 
        // protected MapperRegistry mapperRegistry = new MapperRegistry(this); 
        // The boundType here is the fully qualified name of the mapper interface, and based on this, the jdk dynamic proxy implementation class will be generated later 
        / / 2021-09-09 17:47:57 
        configuration.addMapper ( boundType ) ; } } } }
      
    
  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2021-09-09 17:47:57The place is to generate the jdk dynamic proxy implementation class of mapper, and then add it to the mapping register. The source code is as follows:

org . apache . ibatis . session . Configuration #addMapper
 public  < T >  void  addMapper ( Class < T > type )  { 
  mapperRegistry . addMapper ( type ) ; 
}

org . apache . ibatis . binding . MapperRegistry #addMapper
 public  < T >  void  addMapper ( Class < T > type )  { 
  // Processed only if it is an interface 
  if  ( type . isInterface ( ) )  { 
    if  ( hasMapper ( type ) )  { 
      throw  new  BindingException ( "Type "  + type + " is already known to the MapperRegistry." ) ; 
    } 
    boolean loadCompleted =  false ; 
    try  { 
      // Create a dynamic proxy factory class for the interface and add mapping information 
      // private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); 
      // 2021-09-09 17:47:57 
      knownMappers . put ( type ,  new  MapperProxyFactory < T > ( type ) ) ; 
      . . . snip . . . 
    }  finally  { 
      if ( ! loadCompleted )  { 
        knownMappers . remove ( type ) ; 
      } 
    } 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

2021-09-09 17:47:57MapperProxyFactory is the factory class used to generate mapper dynamic proxy, for specific reference3: MapperProxyFactory.

2.1: mapper node analysis

The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #parse
 public  void  parse ( )  { 
  if  ( ! configuration . isResourceLoaded ( resource ) )  { 
  	// <202107171110> 
    configurationElement ( parser . evalNode ( "/mapper" ) ) ; 
    . . . snip . . . 
  } 
  . . .snip . . . 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

<202107171110>The source code is as follows:

private  void  configurationElement ( XNode context )  { 
  try  { 
    // get namespace 
    // <mapper namespace="yudaosourcecode.mybatis.mapper.TestResultmapAndParametermapMapper"><mapper/> 
    String namespace = context . getStringAttribute ( "namespace" ) ; 
    // mandatory A namespace is required, if it is not set then an exception 
    if  ( namespace . equals ( "" ) )  { 
  	  throw  new  BuilderException ( "Mapper's namespace cannot be empty" ) ;
    } 
    // Set the current namespace value to 
    builderAssistant . setCurrentNamespace ( namespace ) ; 
    // <202107171125> 
    cacheRefElement ( context . evalNode ( "cache-ref" ) ) ; 
    // resolve <cache/> tags 
    // <202107181024> 
    cacheElement ( context . evalNode ( "cache" ) ) ; 
    // Parse the parameter map of 
    parameterMap parameterMapElement ( context . evalNodes ( "/mapper/parameterMap" )) ; 
    // <202107181124> 
    // Parse the parameter configuration of the returned result resultMap 
    resultMapElements ( context . evalNodes ( "/mapper/resultMap" ) ) ; 
    // <202107301052> 
    // Parse the sql tag under mapper 
    sqlElement ( context . evalNodes ( "/mapper/sql" ) ) ; 
    // <202107301409> 
    // Parse add insert delete delete change update check select tag 
    buildStatementFromContext ( context . evalNodes ( "select|insert|update|delete" ) ) ; 
  }  catch  (Exception e )  { 
    throw  new  BuilderException ( "Error parsing Mapper XML. Cause: "  + e , e ) ; 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

<202107181024>at parsing<cache>label, specific reference2.1.2: Parsing cache tags.<202107181124>The place is to parse the resultMap, for specific reference2.1.4: Parse the resultMap node.<202107301052>The place is to parse the sql tag, specific reference2.1.5: Parse sql tags.<202107301409>The place is to parse additions, deletes, modify and check tags, for specific reference2.1.6: Parse CRUD tags.
<202107171125>is parsing<cache-ref>The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #cacheRefElement
 private  void  cacheRefElement ( XNode context )  { 
  if  ( context !=  null )  { 
    // builderAssistant.getCurrentNamespace(): the namespace of the current mapper xml 
    // context.getStringAttribute("namespace "), <cache-ref namepsace="xxx.yyy"> 
	// The namepsace configured in the node 
	// Add the reference relationship of this cache to the configured map 
	// protected final Map<String, String> cacheRefMap = new HashMap< String, String>();     
	configuration . addCacheRef ( builderAssistant . getCurrentNamespace ( ) , context . getStringAttribute ( "namespace" ) ) ; 
	// Use the parameter builderAssistant, and the namespace of the current mapper xml to create 
	// CacheRefResolver object 
    CacheRefResolver cacheRefResolver =  new  CacheRefResolver ( builderAssistant , context . getStringAttribute ( "namespace" ) ) ; 
    try  { 
      // resolve cache-ref 
      // <202107180955> 
  	  cacheRefResolver . resolveCacheRef ( ); 
    }  catch  ( IncompleteElementException e )  { 
  	  configuration . addIncompleteCacheRef ( cacheRefResolver ) ; 
    } 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

<202107180955>reference2.1.1: Parse cache-ref.

2.1.1: Parse cache-ref

The source code is as follows:

org . apache . ibatis . builder . CacheRefResolver #resolveCacheRef
 public  Cache  resolveCacheRef ( )  { 
	return assistant . useCacheRef ( cacheRefNamespace ) ; 
}

org . apache . ibatis . builder . MapperBuilderAssistant #useCacheRef
 public  Cache  useCacheRef ( String namespace )  { 
  // If the namespace of the reference cache is null, throw an exception and tell you that namepsace is required 
  // Exception information of the attribute 
  // as written <cache-ref> but no namespace attribute is specified, the exception here will be triggered 
  if  ( namespace ==  null )  { 
    throw  new  BuilderException ( "cache-ref element requires a namespace attribute." ) ; 
  } 
  try  { 
    unresolvedCacheRef = true ; 
    // Get the cache from the configuration through the namespace, Cache objects are all Cache in namespace units. 
    cache = configuration . getCache ( namespace ) ; // 
    If the referenced cache is not obtained according to the namespace, an exception will be thrown 
    if  ( cache ==  null )  { 
      throw  new  IncompleteElementException ( "No cache for namespace '"  + namespace +  "' could be found." ) ; 
    } 
    currentCache = cache ; 
    unresolvedCacheRef =  false ; 
    return cache; 
  }  catch  ( IllegalArgumentException e )  { 
    throw  new  IncompleteElementException ( "No cache for namespace '"  + namespace +  "' could be found." , e ) ; 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2.1.2: Parsing cache tags

The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #cacheElement
 private  void  cacheElement ( XNode context )  throws  Exception  { 
  if  ( context !=  null )  { 
    // Get the cache type used by the secondary cache, the default is PERPETUAL, which is org.apache .ibatis.cache.impl.PerpetualCache (default provided by the system) 
    // You can also customize 
    String by implementing the interface org.apache.ibatis.cache.Cache type = context . getStringAttribute ( "type" ,  "PERPETUAL") ; 
    // resolve class 
    Class < ?  extends  Cache > typeClass = typeAliasRegistry . resolveAlias ​​( type ) ; 
    // Get cache elimination algorithm, default LRU, or FIFO, WEAK, SOFT 
    String eviction = context . getStringAttribute ( "eviction" ,  "LRU" ) ; 
    // Get the class 
    Class corresponding to the elimination algorithm < ?  extends  Cache > evictionClass = typeAliasRegistry . resolveAlias ​​( eviction) ; 
    // Get the flush interval of the cache, the default is not flushing 
    Long flushInterval = context . getLongAttribute ( "flushInterval" ) ; 
    // Get the size of the cache 
    Integer size = context . getIntAttribute ( "size" ) ; 
    // Get the cache read-only , read-only always returns the same instance, but not safe, non-read-only returns a different instance 
    // but safe, so the default value is false, which is not read-only (note: read-only here is not really read-only, but Said to always be an object, only read-only) 
    boolean readWrite =  ! context . getBooleanAttribute ( "readOnly" ,  false ) ; 
    // Get all child node information, name->value
    Properties props = context . getChildrenAsProperties ( ) ; 
    // <202107181039> 
    // Add new cache 
    builderAssistant . useNewCache ( typeClass , evictionClass , flushInterval , size , readWrite , props ) ; 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

<202107181039>The place is to add a new cache, specific reference2.1.3: Add namespace cache.

2.1.3: Add namespace cache

The source code is as follows:

org . apache . ibatis . builder . MapperBuilderAssistant #useNewCache
 public  Cache  useNewCache ( Class < ?  extends  Cache > typeClass , 
      Class < ?  extends  Cache > evictionClass , 
      Long flushInterval , 
      Integer size , 
      boolean readWrite , 
      Properties props )  { 
  // Get the cache type class, if provided, use provided, otherwise use PerpetualCache 
  typeClass=  valueOrDefault ( typeClass ,  PerpetualCache . class ) ; 
  // The class of the elimination algorithm, if provided, use the provided one, otherwise use LruCache . class 
  evictionClass =  valueOrDefault ( evictionClass ,  LruCache . class ) ; // Construct the cache object 
  Cache through the cache constructor
   cache = new CacheBuilder ( currentNamespace ) . implementation ( typeClass ) . addDecorator ( evictionClass ) . clearInterval (  
      
      
      flushInterval ) 
      . size ( size ) 
      . readWrite ( readWrite ) 
      . properties ( props ) 
      . build ( ) ; 
  // Add to the change collection of the configuration class 
  /*
  org.apache.ibatis.session.Configuration#addCache
  public void addCache(Cache cache) {
    caches.put(cache.getId(), cache);
  }
  */ 
  configuration . addCache ( cache ) ; 
  // assign to the current cache 
  currentCache = cache ; 
  // return the cache 
  return cache ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

2.1.4: Parse the resultMap node

The source code is as follows;

org . apache . ibatis . builder . xml . XMLMapperBuilder #resultMapElements
 private  void  resultMapElements ( List < XNode > list )  throws  Exception  { 
  // loop through all <resultMap/> nodes 
  for  ( XNode resultMapNode : list )  { 
    try  { 
      // < 202107181410> 
      resultMapElement ( resultMapNode ) ; 
    }  catch  (IncompleteElementException e )  { 
      // ignore, it will be retried 
    } 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

<202107181410>The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder # resultMapElement ( org . apache . ibatis . parsing . XNode ) 
private  ResultMap  resultMapElement ( XNode resultMapNode )  throws  Exception  { 
  return  resultMapElement ( resultMapNode ,  Collections . < ResultMapping >  emptyList ( )) ; 
}

private  ResultMap  resultMapElement ( XNode resultMapNode ,  List < ResultMapping > additionalResultMappings )  throws  Exception  { 
  ErrorContext . instance ( ) . activity ( "processing "  + resultMapNode . getValueBasedIdentifier ( ) ) ; 
  // get the value of the id attribute 
  String id = resultMapNode . getStringAttribute ( " id" , 
      resultMapNode.getValueBasedIdentifier ( ) ) ; // get the value of the type attribute String type = resultMapNode . getStringAttribute ( "type" , 
      resultMapNode . getStringAttribute ( "ofType" , resultMapNode 
          . getStringAttribute ( " resultType" , 
              resultMapNode . getStringAttribute ( "javaType" ) ) ) ) ; // Get the value of extend, that is, the inherited resultMap String extend =
  
  
  
  resultMapNode . getStringAttribute ( "extends" ) ; 
  // Get the value of autoMapping, that is, whether the result column that is not configured in resultMap is automatically mapped 
  Boolean autoMapping = resultMapNode . getBooleanAttribute ( "autoMapping" ) ; 
  // Create the type corresponding to the type configured in resultMap class class type object 
  Class < ? > typeClass =  resolveClass ( type ) ; 
  Discriminator discriminator =  null ; 
  List < ResultMapping > resultMappings =  new ArrayList < ResultMapping > ( ) ; 
  resultMappings . addAll ( additionalResultMappings ) ; 
  List < XNode > resultChildren = resultMapNode . getChildren ( ) ; 
  // Get the child nodes of resultMap and loop through the child nodes 
  for  ( XNode resultChild : resultChildren )  { 
    // <202107191613 > 
    // if <constructor> tag 
    if  ( "constructor" . equals ( resultChild .getName ( ) ) )  { 
      processConstructorElement ( resultChild , typeClass , resultMappings ) ; 
    }  else  if  ( "discriminator" . equals ( resultChild . getName ( ) ) )  {  
      // Parse the <discriminator> tag, discriminator, to depend on a column to dynamically assign values ​​to properties 
      // <202107211750> 
      discriminator =  processDiscriminatorElement ( resultChild , typeClass , resultMappings ) ;
    }  else  {  // Here, the description is <id>, <result>, <collection>, <association> and other tags 
      ArrayList < ResultFlag > flags =  new  ArrayList < ResultFlag > ( ) ; 
      // If it is <id> tag, Then add ResultFlag.ID enumeration to flags 
      if  ( "id" . equals ( resultChild . getName ( ) ) )  { 
        flags . add ( ResultFlag . ID ) ; 
      } 
      // <202107211806> 
      resultMappings.add ( buildResultMappingFromContext ( resultChild , typeClass , flags ) ) ; } } // The constructor is as follows: / *
    
  
  
  
 public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
    // map helper class
    this.assistant = assistant;
    this.id = id;
    // entity type
    this.type = type;
    // Inherited resultMap information
    this.extend = extend;
    // discriminator
    this.discriminator = discriminator;
    // All internal node information
    this.resultMappings = resultMappings;
    // Whether to automatically map unconfigured properties
    this.autoMapping = autoMapping;
  }
  */ 
  ResultMapResolver resultMapResolver =  new  ResultMapResolver ( builderAssistant , id , typeClass , extend , discriminator , resultMappings , autoMapping ) ; 
  try  { 
    // <202107211821> 
    return resultMapResolver . resolve ( ) ; 
  }  catch  ( IncompleteElementException   e )  { 
    configuration . addIncompleteResultMap (resultMapResolver ) ; 
    throw e ; 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

<202107191613>Here is the constructor sub-tag that parses the resultMap. The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #processConstructorElement
 private  void  processConstructorElement ( XNode resultChild ,  Class < ? > resultType ,  List < ResultMapping > resultMappings )  throws  Exception  { 
  // Get all child nodes, the following possible configurations: 
  /*
	<resultMap id="myResultMap" type="yudaosourcecode.mybatis.model.TestResultmapConstructor">
	    <constructor>
	        <idArg javaType="integer" jdbcType="INTEGER" column="id"/>
	        <arg javaType="string" jdbcType="VARCHAR" column="myname"/>
	        <arg javaType="integer" jdbcType="INTEGER" column="myage"/>
	    </constructor>
	</resultMap>
  */ 
  List < XNode > argChildren = resultChild . getChildren ( ) ; 
  for  ( XNode argChild : argChildren )  { 
    // ResultFlag is an enumeration class, as follows: 
    /*
	org.apache.ibatis.mapping.ResultFlag
	public enum ResultFlag {
	  ID, CONSTRUCTOR
	}
	*/ 
    ArrayList < ResultFlag > flags =  new  ArrayList < ResultFlag > ( ) ; 
    // first add ResultFlag.CONSTRUCTOR 
    flags . add ( ResultFlag . CONSTRUCTOR ) ; 
    // if idArg, then add ResultFlag.ID 
    if  ( "idArg" . equals ( argChild . getName ( ) ) )  { 
      flags . add ( ResultFlag . ID ); 
    } 
    // <202107191636> 
    // Construct the current child node into a ReusltMapping object and add it to the resultMappings collection 
    resultMappings . add ( buildResultMappingFromContext ( argChild , resultType , flags ) ) ; 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

<202107191636>wherebuildResultMappingFromContextUsed to build ResultMapping objects, specific reference2.2: Build ResultMapping.<202107211750>The discriminator, the source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #processDiscriminatorElement
 private  Discriminator  processDiscriminatorElement ( XNode context ,  Class < ? > resultType ,  List < ResultMapping > resultMappings )  throws  Exception  { 
  // Get various attributes 
  String column = context . getStringAttribute ( "column " ) ;
  String javaType = context . getStringAttribute ( "javaType" ) ; 
  String jdbcType = context . getStringAttribute ( "jdbcType " ) ; 
  String typeHandler = context . getStringAttribute ( "typeHandler" ) ; 
  Class < ? > javaTypeClass =  resolveClass ( javaType ) ; 
  @SuppressWarnings ( "unchecked" ) 
  Class< ?  extends  TypeHandler < ? > > typeHandlerClass =  ( Class < ?  extends  TypeHandler < ? > > )  resolveClass ( typeHandler ) ;
   JdbcType jdbcTypeEnum =  resolveJdbcType ( jdbcType ) ; 
  Map < String ,  String > discriminatorMap =  new  HashMap < String ,  String >( ) ; 
  for  ( XNode caseChild : context . getChildren ( ) )  { 
    // Get the value of the corresponding column of case processing 
    String value = caseChild . getStringAttribute ( "value" ) ; 
    // Get the resultMap used 
    String resultMap = caseChild . getStringAttribute ( "resultMap" ,  processNestedResultMappings ( caseChild , resultMappings ) ) ; 
    discriminatorMap . put ( value , resultMap ) ; 
  } 
  // construct Discriminator 
  return builderAssistant . buildDiscriminator ( resultType , column , javaTypeClass , jdbcTypeEnum , typeHandlerClass , discriminatorMap ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

<202107211806>The buildResultMappingFromContext is to build a ResultMapping object, refer to2.2: Build ResultMapping.<202107211821>specific reference2.3: Parse the resultMap tag as a ResultMap object.

2.1.5: Parse sql tags

The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #sqlElement
 // The list parameter is all sql node information, because multiple mappers can be configured in a namespace, so it is a collection 
private  void  sqlElement ( List < XNode > list )  throws  Exception  { 
  // The datbaseid here is generally obtained from the connection information of the database in mybatis 
  // In addition, the databaseId attribute can also be configured in the sql node 
  if  ( configuration . getDatabaseId ( )  !=  null )  { 
    sqlElement ( list , configuration.getDatabaseId ( ) ) ; } // <202107301107 > sqlElement ( list , null ) ; }
  
  
   

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

<202107301107>The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder # sqlElement ( java . util . List < org . apache . ibatis . parsing . XNode > ,  java . lang . String ) 
private  void  sqlElement ( List < XNode > list ,  String requiredDatabaseId )  throws Exception  { 
  // Loop through all nodes and process them in sequence 
  for  ( XNode context : list )  { 
    // Get the value of the databaseId attribute of the node configuration 
    String databaseId = context . getStringAttribute ( "databaseId" ) ; 
    // Get the value of the node id attribute 
    String id = context . getStringAttribute ( "id" ) ; 
    // Add namespace prefix to generate complete id information, because the id of different namespaces can be repeated, so you need to add namespace prefix 
    id = builderAssistant . applyCurrentNamespace ( id , false ) ; 
    // <202107301123> 
    if  ( databaseIdMatchesCurrent ( id , databaseId , requiredDatabaseId ) ) sqlFragments . put ( id , context ) ; 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

<202107301123>wheredatabaseIdMatchesCurrentThe source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #databaseIdMatchesCurrent
 // The overall logic of the method is complex, you can know the logic 
private  boolean  databaseIdMatchesCurrent ( String id ,  String databaseId ,  String requiredDatabaseId )  { 
  // If the global databseId has value 
  if  ( requiredDatabaseId !=  null )  { 
    // If it is not equal to the databseId property configuration, return false 
    if  ( ! requiredDatabaseId . equals( databaseId ) )  { 
      return  false ; 
    } 
  }  else  { 
    // if there is no global databaseId, but the databaseId property is set, return false 
    if  ( databaseId !=  null )  { 
      return  false ; 
    } 
    // if the sql is already included 
    if  ( this . sqlFragments . containsKey ( id ) )  { 
      // get the contained sql 
      XNode context =  this . sqlFragments . get ( id ); 
      // Get the configured databaseId attribute from the sql node information already contained, and return false 
      if configured if  ( context . getStringAttribute ( "databaseId" )  !=  null )  { 
        return  false ; 
      } 
    } 
  } 
  // Finally return true directly 
  return  true ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

<202107301123>wheresqlFragmentsDefined as follows:

// key: sql node id with namespace, value: node object corresponding to sql node 
private  Map < String ,  XNode > sqlFragments ;
  • 1
  • 2

2.1.6: Parse CRUD tags

The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder # buildStatementFromContext ( java . util . List < org . apache . ibatis . parsing . XNode > ) 
private  void  buildStatementFromContext ( List < XNode > list )  { 
  // databaseId if requested 
  if  ( configuration.getDatabaseId _ _( )  !=  null )  { 
    buildStatementFromContext ( list , configuration . getDatabaseId ( ) ) ; 
  } 
  // <202107301506> 
  // build expression 
  buildStatementFromContext ( list ,  null ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

<202107301506>The place is to build the statement, the source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder # buildStatementFromContext ( java . util . List < org . apache . ibatis . parsing . XNode > ,  java . lang . String ) 
private  void  buildStatementFromContext ( List < XNode > list ,  String requiredDatabaseId)  { 
  // Traverse the <insert><delete><update><select> nodes in turn and perform parsing 
  for  ( XNode context : list )  { 
  	// <202107301526> 
    final  XMLStatementBuilder statementParser =  new  XMLStatementBuilder ( configuration , builderAssistant , context , requiredDatabaseId ) ; 
    try  { 
      statementParser . parseStatementNode ( ) ; 
    }  catch  ( IncompleteElementException e ) {
      // If the parsing fails, add it to the incomplete statement collection, and try to parse it again later 
      // protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>(); 
      configuration . addIncompleteStatement ( statementParser ) ; 
    } 
  } 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

<202107301526>The place is to create XMLStatementBuilder and perform specific parsing, refer to this article for details .

2.2: Build ResultMapping

The source code is as follows:

org . apache . ibatis . builder . xml . XMLMapperBuilder #buildResultMappingFromContext
 private  ResultMapping  buildResultMappingFromContext ( XNode context ,  Class < ? > resultType ,  ArrayList < ResultFlag > flags )  throws  Exception  { 
  // Get various attributes 
  String property = context . getStringAttribute ( "property" ) ;
  String column = context . getStringAttribute ( "column" ) ; 
  String javaType = context . getStringAttribute ( "javaType" ) ; 
  String jdbcType = context . getStringAttribute ( "jdbcType " ) ; 
  String nestedSelect = context . getStringAttribute ( "select" ) ; 
  String nestedResultMap = context .getStringAttribute ( "resultMap" , 
      processNestedResultMappings ( context ,  Collections . < ResultMapping >  emptyList ( ) ) ) ; 
  String notNullColumn = context . getStringAttribute ( "notNullColumn" ) ; 
  String columnPrefix = context . getStringAttribute ( "columnPrefix" ) ; 
  String typeHandler = context .getStringAttribute ( "typeHandler" ) ; 
  String resulSet = context . getStringAttribute ( "resultSet" ) ; 
  String foreignColumn = context . getStringAttribute ( "foreignColumn" ) ; 
  boolean lazy =  "lazy" . equals ( context . getStringAttribute ( "fetchType" , configuration . isLazyLoadingEnabled ( )  ?  "lazy" :  "eager" ) ) ; 
  // resolve javaType to corresponding class 
  Class < ? > javaTypeClass =  resolveClass ( javaType ) ; 
  // resolve typeHandler to corresponding class @SuppressWarnings 
  ( " unchecked" ) 
  Class < ?  extends  TypeHandler < ? > > typeHandlerClass =  ( Class < ?  extends  TypeHandler < ? > > )  resolveClass ( typeHandler) ;
   ,// resolve jdbcType to the corresponding org.apache.itabis.type.JdbcType enumeration class
   JdbcType jdbcTypeEnum =  resolveJdbcType ( jdbcType ) ; 
  // <202107191647> 
  return builderAssistant . buildResultMapping ( resultType , property , column , javaTypeClass , jdbcTypeEnum , nestedSelect , nestedResultMap , notNullColumn , columnPrefix , typeHandlerClass , flags , resulSetforeignColumn  ,lazy ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

<202107191647>The first step is to construct ResultMapping through the mapping construction helper class. The source code is as follows:

org . apache . ibatis . builder . MapperBuilderAssistant #buildResultMapping
 public  ResultMapping  buildResultMapping ( 
      Class < ? > resultType , 
      String property , 
      String column , 
      Class < ? > javaType ,
       JdbcType jdbcType , 
      String nestedSelect , 
      String nestedResultMap , 
      String notNullColumn , 
      StringcolumnPrefix , 
      Class < ?  extends  TypeHandler < ? > > typeHandler , 
      List < ResultFlag > flags , 
      String resultSet , 
      String foreignColumn ,  
      boolean lazy )  { 
  // resolve java type class 
  // <202107191656> 
  Class < ? > javaTypeClass =  resolveResultJavaType ( resultType , property , javaType ) ;
  // Resolve a type handler class (database <->pojo property handler) 
  // <202107191704> TypeHandler
   < ?> typeHandlerInstance = resolveTypeHandler ( javaTypeClass , typeHandler ) ; // resolve the column property in the <constructor> tag // <202107211419> List < ResultMapping > composites = parseCompositeColumnName ( column ) ; // If column is configured, assign the value of column to null if ( composites . size ( ) > 0 ) column = null 
  
  
   
  
      ; 
  // configuration: configuration object 
  // property: the property property value of the 
  current label // column: the column property value of the 
  current label // javaTypeClass: the class corresponding to the javaType of the current label 
  // Create the result mapping builder object 
  ResultMapping . Builder builder =  new  ResultMapping . Builder ( configuration , property , column , javaTypeClass ) ; 
  // Set various properties 
  builder . jdbcType ( jdbcType ) ; 
  // Set the embedded query id, that is, the id of other statements, and finally generate a named The id of the space 
  // Parse the select attribute, that is, the statement of the other or its own namespace that is referenced
  // Such as <collection ... select="yudaosourcecode.mybatis.mapper.TestResultmapCollectionManyMapper.getById1" .../> 
  // I think since it is a reference to other statements, then write the full path regardless of whether it is consistent with the current namespace It will be very clear, the code is as follows: 
  /*
  org.apache.ibatis.builder.MapperBuilderAssistant#applyCurrentNamespace
  public String applyCurrentNamespace(String base, boolean isReference) {
    if (base == null) return null;
    if (isReference) {
      // is it qualified with any namespace yet?
      if (base.contains(".")) return base;
    } else {
      // is it qualified with this namespace yet?
      if (base.startsWith(currentNamespace + ".")) return base;
      if (base.contains(".")) throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
    }
    return currentNamespace + "." + base;
  }
  */ 
  builder . nestedQueryId ( applyCurrentNamespace ( nestedSelect ,  true ) ) ; 
  // set the embedded resultMap id (with namespace) 
  // eg: <collection .. resultMap="xxx.yyy.zzz.someOtherResultMapId" ... /> 
  builder . nestedResultMapId ( applyCurrentNamespace ( nestedResultMap ,  true ) ) ; 
  builder . resultSet ( resultSet ) ; 
  builder . typeHandler ( typeHandlerInstance ) ;
  builder . flags ( flags ==  null  ?  new  ArrayList < ResultFlag > ( )  : flags ) ; 
  builder . composites ( composites ) ; 
  builder . notNullColumns ( parseMultipleColumnNames ( notNullColumn ) ) ; 
  builder . columnPrefix ( columnPrefix ) ; 
  builder . foreignColumn (foreignColumn ) ; 
  builder . lazy ( lazy ) ; 
  // construct a ResultMapping object and return 
  return builder . build ( ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69

<202107191656>The corresponding java type of service analysis, the source code is as follows:

org . apache . ibatis . builder . MapperBuilderAssistant #resolveResultJavaType
 private  Class < ? >  resolveResultJavaType ( Class < ? > resultType ,  String property ,  Class < ? > javaType )  { 
  // If the incoming javaType and property are not null 
  if  ( javaType ==  null  && property !=  null )  { 
    try  {
      // Through the reflection package tool class, get the class type of the property 
      MetaClass metaResultType =  MetaClass . forClass ( resultType ) ; 
      javaType = metaResultType . getSetterType ( property ) ; 
    }  catch  ( Exception e )  { 
      //ignore, following null check statement will deal with the situation 
    } 
  } 
  // If this javaType is still null, assign the value to Object.class 
  if  ( javaType ==  null )  { 
    javaType =  Object.class ; } return javaType ; 
  } _
  

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

<202107191704>The source code is as follows:

org . apache . ibatis . builder . BaseBuilder #resolveTypeHandler
 protected  TypeHandler < ? >  resolveTypeHandler ( Class < ? > javaType ,  Class < ?  extends  TypeHandler < ? > > typeHandlerType )  { 
  // is null, indicating that the user has not set type handler for this mapping Handler , return null directly 
  if  ( typeHandlerType ==  null )  return  null ; 
  // Get the type handler from the type handler register
  TypeHandler < ? > handler = typeHandlerRegistry . getMappingTypeHandler ( typeHandlerType ) ; 
  if  ( handler ==  null )  { 
    // If there is no type handler registry, instantiate a 
    handler = typeHandlerRegistry . getInstance ( javaType , typeHandlerType ) ; 
  } 
  return handler ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

<202107211419>at parsingThe column attribute in <collection> in <resultMap>, the source code is as follows:

org . apache . ibatis . builder . MapperBuilderAssistant #parseCompositeColumnName
 private  List < ResultMapping >  parseCompositeColumnName ( String columnName )  { 
  List < ResultMapping > composites =  new  ArrayList < ResultMapping > ( ) ; 
  // Associated query configures multiple mappings, possible values ​​"{oneid =id,outerManyName=manyname}" 
  // if contains "=", and contains "," 
  if  ( columnName !=  null  && ( columnName . indexOf ( '=' )  >  - 1  || columnName . indexOf ( ',' )  >  - 1 ) )  { 
  	// Respectively parse the attribute mapping relationship configured at the place 
    StringTokenizer parser =  new  StringTokenizer ( columnName ,  "{}= , " ,  false ) ; 
    while  ( parser . hasMoreTokens ( ) )  { 
      String property = parser. nextToken ( ) ; 
      String column = parser . nextToken ( ) ; 
      ResultMapping . Builder complexBuilder =  new  ResultMapping . Builder ( configuration , property , column , configuration . getTypeHandlerRegistry ( ) . getUnknownTypeHandler ( ) ) ; 
      // Create a result map object and add it to The collection to return 
      composites.add (complexBuilder . build ( ) ) ; 
    } 
  } 
  return composites ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

2.3: Parse the resultMap tag as a ResultMap object

The source code is as follows:

org . apache . ibatis . builder . ResultMapResolver #resolve
 public  ResultMap  resolve ( )  { 
  return assistant . addResultMap ( this . id ,  this . type ,  this . extend ,  this . discriminator ,  this . resultMappings ,  this . autoMapping ) ; 
}

org . apache . ibatis . builder . MapperBuilderAssistant #addResultMap
 public  ResultMap  addResultMap ( 
      String id , 
      Class < ? > type , 
      String extend , 
      Discriminator discriminator , 
      List < ResultMapping > resultMappings , 
      Boolean autoMapping )  { 
  // Add mapper xml namespace prefix 
  id = applyCurrentNamespace ( id,  false ) ; 
  // Add the namespace prefix of mapper xml to 
  extend extend =  applyCurrentNamespace ( extend ,  true ) ; 
  // ResultMap constructor 
  ResultMap . Builder resultMapBuilder =  new  ResultMap . Builder ( configuration , id , type , resultMappings , autoMapping ) ; 
  / / if there is an extend 
  if  ( extend !=  null )  { 
    // check if there is a resultMap of the extend, if not, an exception
    if  ( ! configuration . hasResultMap ( extend ) )  { 
      throw  new  IncompleteElementException ( "Could not find a parent resultmap with id '"  + extend +  "'" ) ; 
    } 
    // Get the ResultMap corresponding to the extend from the configuration 
    ResultMap resultMap = configuration . getResultMap ( extend ) ; 
    // Get all ResultMappings 
    List in the inherited ResultMap < ResultMapping > extendedResultMappings =  new ArrayList < ResultMapping > ( resultMap . getResultMappings ( ) ) ; 
    // Delete the inherited 
    extendedResultMappings . removeAll ( resultMappings ) ; 
    // Determine if the current ResultMap declares <construcotr> 
    boolean declaresConstructor =  false ; 
    for  ( ResultMapping resultMapping : resultMappings )  { 
      // declared, set the flags to true 
      if  ( resultMapping . getFlags ( ) .contains ( ResultFlag . CONSTRUCTOR ) )  { 
        declaresConstructor =  true ; 
        break ; 
      } 
    } 
    // contains <constructor> declarations, cyclically inherited, delete declarations inside 
    if  ( declaresConstructor )  { 
      Iterator < ResultMapping > extendedResultMappingsIter = extendedResultMappings . iterator ( ) ; 
      while  ( extendedResultMappingsIter.hasNext ( ) ) { if ( _ _ 
         extendedResultMappingsIter . next ( ) . getFlags ( ) . contains ( ResultFlag . CONSTRUCTOR ) )  { 
          extendedResultMappingsIter . remove ( ) ; 
        } 
      } 
    } 
    // Add inherited ResultMapping information to current 
    resultMappings . addAll ( extendedResultMappings ) ; 
  } 
  resultMapBuilder . discriminator ( discriminator ) ; 
  // Construct ResultMap
  ResultMap resultMap = resultMapBuilder . build ( ) ; 
  // Add the ResultMap object corresponding to the <resultMap/> tag to the configuration 
  configuration . addResultMap ( resultMap ) ; 
  // Return the parsed ResultMap object 
  return resultMap ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60

3: MapperProxyFactory

This is a factory class used to generate the mapper interface jdk dynamic proxy. The source code is as follows:

org . apache . ibatis . binding . MapperProxyFactory 
public  class  MapperProxyFactory < T >  { 
  // mapper interface 
  private  final  Class < T > mapperInterface ; 
  private  final  Map < Method ,  MapperMethod > methodCache =  new  ConcurrentHashMap < Method ,  MapperMethod > ( ) ;
  
  public  MapperProxyFactory ( Class < T > mapperInterface )  { 
    this . mapperInterface = mapperInterface ; 
  } 
  // Get the proxy's mapper interface 
  public  Class < T >  getMapperInterface ( )  { 
    return mapperInterface ; 
  }

  public  Map < Method ,  MapperMethod >  getMethodCache ( )  { 
    return methodCache ; 
  }

  // Create mapper interface proxy class 
  @SuppressWarnings ( "unchecked" ) 
  protected  T  newInstance ( MapperProxy < T > mapperProxy )  { 
    return  ( T )  Proxy . newProxyInstance ( mapperInterface . getClassLoader ( ) ,  new  Class [ ]  { mapperInterface } , mapperProxy ) ; 
  }

  public  T  newInstance ( SqlSession sqlSession )  { 
    // Generate the java.lang.reflect.InvocationHandler class MapperProxy used to execute the method in the jdk dynamic proxy 
    //, this class is the core class, and the invoke method here will be executed when performing database operations 
    // 2021-09-09 17:58:56 
    final  MapperProxy < T > mapperProxy =  new  MapperProxy < T > ( sqlSession , mapperInterface , methodCache ) ; 
    return  newInstance ( mapperProxy ) ; 
  }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

2021-09-09 17:58:56is to createorg.apache.ibatis.bingding.MapperProxy, which is used to generate the mapper interface dynamic proxy classjava.lang.reflect.InvocationHandlerImplementation class. Let's focus on theinvokeThe implementation of the method, the specific reference3.1: MaperProxy#invoke.

3.1: MaperProxy#invoke

When we execute the following code:

String resource =  "mybatis/mybatis-config-1.xml" ; 
InputStream inputStream =  Resources . getResourceAsStream ( resource ) ; 
SqlSessionFactory sqlSessionFactory =  new  SqlSessionFactoryBuilder ( ) . build ( inputStream ) ; 
this . sqlSessionFactory = sqlSessionFactory ; 
this . sqlSession =  this . sqlSessionFactory.openSession _ _( ) ; 
this . testExtendAndAutoMappingMapper = 
        this . sqlSession . getMapper ( TestExtendAndAutoMappingMapper . class ) ;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

When obtaining the implementation class of the mapper interface, what is returned is the generated dynamic proxy class based on jdk. Through the previous analysis, we know that its InvocationHandler usesMaperProxy, so when the mapper method is called, it will first executeorg.apache.ibatis.binding.MapperProxy#invoke, the source code is as follows:

org . apache . ibatis . binding . MapperProxy #invoke
 public  Object  invoke ( Object proxy ,  Method method ,  Object [ ] args )  throws  Throwable  { 
  // toString, equals, hashCode and other methods defined by Object classes 
  if  ( Object . class . equals ( method . getDeclaringClass ( ) ) )  { 
    try  {
      // The method of the Object class is called, directly call and return the 
      return method . invoke ( this , args ) ; 
    }  catch  ( Throwable t )  { 
      throw  ExceptionUtil . unwrapThrowable ( t ) ; 
    } 
  } 
  // Create MapperMethod, which encapsulates the interface Information, method information, and configuration object information of the global configuration file 
  // The source code is as follows: 
  /*
  private MapperMethod cachedMapperMethod(Method method) {
    MapperMethod mapperMethod = methodCache.get(method);
    if (mapperMethod == null) {
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      methodCache.put(method, mapperMethod);
    }
    return mapperMethod;
  }
  */ 
  final  MapperMethod mapperMethod =  cachedMapperMethod ( method ) ; 
  // Execute through MapperMethod object 
  // 2021-09-10 15:12:30 
  return mapperMethod . execute ( sqlSession , args ) ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

2021-09-10 15:12:30throughorg.apache.ibatis.bingding.MaperMethodObject execution, specific reference3.2::MapperMethod#execute.

3.2::MapperMethod#execute

The source code is as follows:

org . apache . ibatis . binding . MapperMethod #execute
 public  Object  execute ( SqlSession sqlSession ,  Object [ ] args )  { 
  Object result ; 
  // According to the corresponding statement type, call the API execution of the session object org.apache.ibatis.session.SqlSession 
  // Specific database operations 
  // 2021-09-10 15:17:17 
  if  ( SqlCommandType . INSERT == command . getType ( ) )  { 
    Object param= method . convertArgsToSqlCommandParam ( args ) ; 
    result =  rowCountResult ( sqlSession . insert ( command . getName ( ) , param ) ) ; 
  }  else  if  ( SqlCommandType . UPDATE == command . getType ( ) )  { 
    Object param = method . convertArgsToSqlCommandParam (args ) ; 
    result =  rowCountResult ( sqlSession . update ( command . getName ( ) , param ) ) ; 
  }  else  if  ( SqlCommandType . DELETE == command . getType ( ) )  { 
    Object param = method . convertArgsToSqlCommandParam ( args ) ; 
    result =  rowCountResult( sqlSession . delete ( command . getName ( ) , param ) ) ; 
  }  else  if  ( SqlCommandType . SELECT == command . getType ( ) )  { 
    if  ( method . returnsVoid ( )  && method . hasResultHandler ( ) )  { 
      executeWithResultHandler ( sqlSession , args ); 
      result =  null ; 
    }  else  if  ( method . returnsMany ( ) )  { 
      result =  executeForMany ( sqlSession , args ) ; 
    }  else  if  ( method . returnsMap ( ) )  { 
      result =  executeForMap ( sqlSession , args ) ; 
    }  else  { 
      Object param =method . convertArgsToSqlCommandParam ( args ) ; 
      result = sqlSession . selectOne ( command . getName ( ) , param ) ; 
    } 
  }  else  if  ( SqlCommandType . FLUSH == command . getType ( ) )  { 
      result = sqlSession . flushStatements ( ) ; 
  }  else  {
    throw  new  BindingException ( "Unknown execution method for: "  + command . getName ( ) ) ; 
  } 
  if  ( result ==  null  && method . getReturnType ( ) . isPrimitive ( )  &&  ! method . returnsVoid ( ) )  { 
    throw  new  BindingException ( " Mapper method '"  + command . getName ( ) 
        +  " attempted to return null from a method with a primitive return type ("  + method . getReturnType ( )  +  ")." ) ; 
  } 
  return result ; 
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38

2021-09-10 15:17:17is to call the session object according to the type of user commandorg.apache.ibatis.session.SqlSessionThe different methods of the method will call the subclass of the executor inside the methodorg.apache.ibatis.executor.CachingExecutorTo perform subsequent operations, for this calling process, you can refer to this article .

write on the back

Looking at the source code is really boring! ! ! However, only those who can endure loneliness can achieve something again! !

Tags: Mybatis global configuration file <mappers> tag analysis

mybatis mybatis java

Related: Mybatis global configuration file <mappers> tag analysis