How is Mapper associated with XMl in Mybatis

From the source to analyze, through Mybatis all know, must specify nameSpace Mapper fully qualified class name. So you can relate it. The implementation of Mapper must be dynamic Pitching, enhanced in the InvocationHandler. So here’s how you do it. Okay? Analysis of the design of many things, easy to go astray. I tried to get back to the point.

1. Parse XML files

The XML parsing here is a bit tedious, so if you parse it line by line, it’s a lot, so let’s pick the main line here. Then it will be divided into sections and topics for analysis.

Parse the total configuration file

If you start with the classic Mybatis create SqlSessionFactory, you will see the following code

Some comments inside the code, I read the source code to write, some write more outrageous. There were some notes of confusion when I looked at them earlier. And then I read it again. So it’s going to stay here.

  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties")); // Parse the properties tag and place it in the Variables of Parser and config
      Properties settings = settingsAsProperties(root.evalNode("settings"));// Load the setting TAB
      loadCustomVfs(settings); // what is the VFS in lcnote? How do I know this
      VFS (virtual File System) abstracts several apis to access resources on the file system; For example, in
      // Parse mapperElement(root.evalNode("mappers")); If you specify package, you can get all the class files under the package path through the VFS.
      // It will be added to mappe, similar to the classPathSacnner in Spring, which can specify filters.


      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));

      // From here on, we parse the specific tag, new out the object, set the attribute under the tag,
      Mybatis (mybatis) objectFactory (mybatis) objectFactory (mybatis) The objectFactory, plugins
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectFactory"));
      reflectorFactoryElement(root.evalNode("objectFactory"));

      // Set the setting tag
      settingsElement(settings);

      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));

      //lcnote
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "+ e, e); }}Copy the code

See how to parse mapper files directly.

As you can see from this code, there are two types of tags that can be written under the Mappers tag. Package and Mapper tags. There are different parsing methods for both.

Parsing the package tag

Here is only a partial intercept of the source code. Will also be several source splicing in a piece, easy to see

 if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        }
// Here is the configuration.addMappers(mapperPackage) method
public void addMappers(String packageName) {
  //mapperRegistry is a registry that registers a mapper and maintains a large number of all mapper objects.
    mapperRegistry.addMappers(packageName);
  }
/ / here is mapperRegistry. AddMappers (packageName);
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
// addMappers(packageName, Object.class); Method,

//packageName Indicates the path of the package to be scanned
//superType indicates that the class to be sought is a subclass of that class.

  public void addMappers(String packageName, Class
        superType) 
  {
    //resolverUtil is a utility class that finds subclasses of the specified class in the specified package.ResolverUtil<Class<? >> resolverUtil =new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    
    // Find the appropriate Class and add it to mapper.Set<Class<? extends Class<? >>> mapperSet = resolverUtil.getClasses();for (Class<?> mapperClass : mapperSet) {
      addMapper(mapperClass);
    }
  }
//resolverUtil.find(new ResolverUtil.IsA(superType), packageName);

// Scan all classes under the given package, including those under the subpath. Call the Test method to match the matched class, and call getClasses to retrieve the matched class.
  public ResolverUtil<T> find(Test test, String packageName) {
    String path = getPackagePath(packageName);

    try {
      List<String> children = VFS.getInstance().list(path);
      for (String child : children) {
        if (child.endsWith(".class")) {
          // Load the class object and call the static inner class IsA (which implements the Test interface) in ResolverUtil for matching.addIfMatching(test, child); }}}catch (IOException ioe) {
      log.error("Could not read package: " + packageName, ioe);
    }

    return this;
  }
//addIfMatching(test, child);

  protected void addIfMatching(Test test, String fqn) {
    try {
      String externalName = fqn.substring(0, fqn.indexOf('. ')).replace('/'.'. ');
      ClassLoader loader = getClassLoader();
      if (log.isDebugEnabled()) {
        log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]"); } Class<? > type = loader.loadClass(externalName);if(test.matches(type)) { matches.add((Class<T>) type); }}catch (Throwable t) {
      log.warn("Could not examine class '" + fqn + "'" + " due to a "
          + t.getClass().getName() + " with message: "+ t.getMessage()); }}/ / * * * * * * * * * * * * * * * * * * * * * * * * * * * * on * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
// public 
      
        void addMapper(Class
       
         type) to load the appropriate Class instantiation into mapperRegistry.
       
      
// And this method is in mapperRegistry.
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {Mapper can be registered only once
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        
        // Use mapper new to create MapperProxyFactory and place it under knownMappers
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // It is important to add the type before parsing, so he will automatically try binding to parse mapper. If the type knows, it's okay,
        // Mapper annotates mapper.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code

There are several important classes that appear in this

  1. MapperRegistry: A registry that holds all mapper files.
  2. Configuration: The Configuration object reported an error in all data available during Mybatis runtime.
  3. MapperProxyFactory: creates a proxy mapper factory. There is nothing special about this, it calls the method of creating proxy objects to create objects.
  4. MapperAnnotationBuilder: Parses annotations in mapper. These annotations do the same thing as XMl, butNot recommended.
Parse mapper labels

When I looked at the source code, I was like, wow, this works like this, this framework has this functionality.

{		

          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if(resource ! =null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
          
            
            // The loading operation of resource and URL is always the same, but the source of resource is different.
            
            
            // Load the resource, parse the mapper file, and build the mapperStatement object.
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();//lcnote parsing is the same as configuration file parsing. Both build the XMLMapperBuilder and then call the parse method}}else if (resource == null&& url ! =null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = newXMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); }}else if (resource == null && url == null&& mapperClass ! =null) {
            
            // There is nothing special here, just the process of parsing the package tag to get the mapper,Class<? > mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); }else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); }}Copy the code

As you can see, three attributes, resource, URL, and class, are supported, and the load order is resource first, URL, and class, and all three cannot be specified simultaneously.

Resource and URL loading operations are the same, that is, the source of resource is different. The loading of the class is the same as the parsing of the package tag to get the mapper, XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, Configuration, resource, configuration.getSqlFragments()); began

Build XMLMapperBuilder

BaseBuilder is a basic class in Mybatis. A lot of analysis is inherited with him, just began to do analysis.

Just a little bit more, MapperBuilderAssistant is really a utility class, so take a look at its construction

public class MapperBuilderAssistant extends BaseBuilder {
  private String currentNamespace; // The nameSpace currently parsed
  private final String resource;  // The resource file of the current nameSpace
  private Cache currentCache;     // The current cache, corresponding to the cache tag in the mapper tag.
  private boolean unresolvedCacheRef; // issue #676
}
Copy the code

This class corresponds to everything that is generated when a Mapper file is parsed. Such as resultMap, SQL, SELECT, Update, and so on. These are related things. Will be added to the BaseBuilder through this object.

Inherits from BaseBuilder, XMLMapperBuilder is primarily used to parse mapper tags in mappers in configuration files.

// Look at the constructor class
  public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
        configuration, resource, sqlFragments);
  }

  private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
    super(configuration);
    this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
    this.parser = parser;
    this.sqlFragments = sqlFragments;
    this.resource = resource;
  }

// the super constructor
 public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();// Obtain the contents of typeAliases tags from the configuration file
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();Fetch The typeHandlers from the configuration file} there is nothing to say about the constructor, but the following Parse methodCopy the code

Several important classes appear above

  1. XMLMapperEntityResolver: An entity resolver for xmlMapper. Inheritance andEntityResolver. It is an object in the org.xml.sax package.
  2. BaseBuilder: The base class for all XML parsing.
Call the parse method of XMLMapperBuilder
  public void parse(a) {
     // The configuration file contains the loaded resource set
    if(! configuration.isResourceLoaded(resource)) {// Go to a set in configuration
      
      // This is the key, the key is to parse the mapper tag
      configurationElement(parser.evalNode("/mapper"));// Parse the mapper tag
      configuration.addLoadedResource(resource);// Add to the already loaded collection
      bindMapperForNamespace(); // Try to load the configuration file through nameSpace.
      // Note that the nameSpace does not have to be consistent with the Mapper interface.
    }
    
    // The following operation is also interesting.
    / / to parse the XML, if an error (IncompleteElementException) won't be thrown immediately, but the error will be cached, in the above all after parsing is complete, in give it a try.
    // 
    parsePendingResultMaps(); 
    parsePendingCacheRefs(); 
    parsePendingStatements(); 
  }

Copy the code

The main thing here is to parse the Mapper tag and try to load the corresponding Mapper through nameSpace. If it is loaded, the MapperRegistry above is called to register the Mapper there.

The parse operation is similar to the previous one for the Configuration label. The parent label is parsed first and then the child label is parsed. Now let’s see how it’s resolved. Okay

private void configurationElement(XNode context) {
    try {
      / / namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));// Parse each tag element
      // Parse the cache label
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      / / parse SQL
      sqlElement(context.evalNodes("/mapper/sql"));
      
      / / waring here is very important, the real start parsing the select | insert | update | delete tags
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: "+ e, e); }}Copy the code

instructions

  1. Parse parameterMap. After parsing parameterMap above, build ParameterMapping and add it to builderAssistant. This is converted to ParameterMap in the builderAssistant and finally added to the Configuration object’s parameterMaps property. This Configuration is universal. And you can have multiple parameterMap tags in a mapper.

  2. Build a cache object, add it to configuration, and assign currentCache in builderAssistant to the currentCache object. And a Mapper can have only one cache label.

  3. Parsing a resultMap is a bit more complicated than the previous two because there are many tags underneath the resultMap.

     for (XNode resultChild : resultChildren) {
          if ("constructor".equals(resultChild.getName())) {
            // This is very simple, through the constructor to set parameters
            processConstructorElement(resultChild, typeClass, resultMappings);
          } else if ("discriminator".equals(resultChild.getName())) {
            // The Discriminator object corresponding to lcNote. In reality, the Discriminator tag is not used. After a look, it seems that this tag can implement the function of Swtich Case and can also be paired with a resultMap
            // Let's do something fun. This has actually worked before
            discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
          } else {
            List<ResultFlag> flags = new ArrayList<>();
            if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); }}Copy the code
    • For the Constructor tag, build a ResultMapping object to add to the ResultMapping collection.
    • I haven’t used discriminator tags, but I’ll do that later. buildDiscriminatorObject.
    • For other tags, build a ResultMapping object to add to the ResultMapping collection.

    This is a resultMap label below, in after parsing a resultMap, will assemble ResultMapResolver above related objects, invoke ResultMapResolver. Resolve (); Method to build a ResultMap object and place it in the Configuration. Therefore, ResultMap is the entity class corresponding to the ResultMap tag

    1. Parse SQL tags. Put xNode and ID (the ID specified by the SQL tag) into sqlFragments on the XMLMapperBuilder object. SqlFragments are StrictMap extensions and HashMap that override the PUT and GET methods. This is mostly done with put and get. SqlFragments are used to store SQL fragments. Note that parsing these fragments does not deal with dynamic tags in SQL. Remember that dynamic labels are determined by parameters. Here’s just a simple way to store it. And SQL tags are multiple.

    2. Parsing the select | insert | update | delete tags. That’s the point. In order to clear, still, will look at the source select | insert | update | delete tags are multiple. So this is loop parsing, and the following code is just parsing inside the body of the loop.

        public void parseStatementNode(a) {
          String id = context.getStringAttribute("id");
          String databaseId = context.getStringAttribute("databaseId");
          // You can specify the dataBase in mybatis, and you can also specify the databaseID in mybatis.
          // This is a judgment, if not currently applied, will not parse.
          if(! databaseIdMatchesCurrent(id, databaseId,this.requiredDatabaseId)) {
            return;
          }
         
          String nodeName = context.getNode().getNodeName();
          // Determine the type of SQL by the name of the tag.
          SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
          boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
          
          // If flushCache is not specified and the type is select, the default value is false.
          boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
          // If useCache is not specified and the type is select, the default is true.
          boolean useCache = context.getBooleanAttribute("useCache", isSelect);
          
          // What does this label mean? This is still recommended to see the official documentation of Mybatis.
          boolean resultOrdered = context.getBooleanAttribute("resultOrdered".false);
      
          // Include Fragments before parsing
          // Want to parse the includ tag before parsing the SQL. You can tell by the name. This is what you do with 
                 
                   tags.
                 
          // This explains why it is so easy to parse SQL tags. SQL tags will eventually be used in statements.
          // Dynamic SQL is also written in Statement, so include it before you actually start parsing tags. A piece of on
          // In the following parsing operation. A resolution
          XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
          includeParser.applyIncludes(context.getNode());
         
          String parameterType = context.getStringAttribute("parameterType"); Class<? > parameterTypeClass = resolveClass(parameterType); String lang = context.getStringAttribute("lang");
          LanguageDriver langDriver = getLanguageDriver(lang);
      
          // Parse selectKey after includes and remove them.
          / / selectkey
          processSelectKeyNodes(id, parameterTypeClass, langDriver);
          // LCnote parse SQL selectKey is removed before parse
          // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
          
          
          
          // useGeneratedKeys is required, and a cache is maintained. So let's see, id is the ID of the selcet tag
          KeyGenerator keyGenerator;
          String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
          keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); // eg:org.apache.ibatis.domain.mybatis.mapper.StudentMapper.listAllStudent! selectKey
          if (configuration.hasKeyGenerator(keyStatementId)) {
            keyGenerator = configuration.getKeyGenerator(keyStatementId);
          } else {
            keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
                configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
                ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          }
         // To be honest, I really don't know what the langDriver of Mybatis is
          SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
          StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
          Integer fetchSize = context.getIntAttribute("fetchSize");
          Integer timeout = context.getIntAttribute("timeout");
          String parameterMap = context.getStringAttribute("parameterMap");
          String resultType = context.getStringAttribute("resultType"); Class<? > resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap");
          String resultSetType = context.getStringAttribute("resultSetType");
          ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
          if (resultSetTypeEnum == null) {
            resultSetTypeEnum = configuration.getDefaultResultSetType();
          }
          String keyProperty = context.getStringAttribute("keyProperty");
          String keyColumn = context.getStringAttribute("keyColumn");
          String resultSets = context.getStringAttribute("resultSets");
       
          
          // Add the assembled MappedStatement to the builderAssistant
          // The map of the statement is maintained in the configuration file. The key is the ID of the namespace and mapper.
          builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
              fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
              resultSetTypeEnum, flushCache, useCache, resultOrdered,
              keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
      Copy the code

Calling the parse method of XMLMapperBuilder parses the mapper.xml file

  1. The first thing to know is that there must be a corresponding object in Mybatis for each XML element in the data range.
  2. Aliases and type handlers are definitely used when working with XML. These two are generic. We’ll see that later.
  3. Add parsed objects to configuration.
  4. It’s interesting to parse mapper files. For example, you can use ${} to refer to environment variables in SQL tags. Nested references are also supported.
  5. Indeed, when I looked at the source code, I found that there was this kind of usage, and some things had not been used. Check it out in detail later
  6. If an error is reported during parsing, it will not be thrown immediately. Instead, put it in a collection and try to parse it once all the XML files have been parsed.

Try binding a Mapper to a nameSpace

  private void bindMapperForNamespace(a) {
     // As mentioned earlier, builderAssistant corresponds to a utility class during mapper parsing. Get a namespace
    String namespace = builderAssistant.getCurrentNamespace();
    if(namespace ! =null) { Class<? > boundType =null;
      try {
        // Try to load by fully qualified class name
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if(boundType ! =null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        configuration.addLoadedResource("namespace:"+ namespace); configuration.addMapper(boundType); }}}Copy the code

When parsing the XML file, it generates the corresponding tags and adds them to the Configuration. Then it loads the class class through a nameSpace. If the nameSpace is to correspond to the Mapper, it must be the same. That’s all right. Write whatever you want.

Add the loaded class to configuration. Configuration maintains a map, key is class, value is MapperProxyFactory. Attention should be paid to the configuration. AddMapper (boundType); Methods. And we’re going to look at this method.

The nameSpace in the Mapper tag is cached. The proxy object creation factory is also generated.

This method is in MapperRegistry.


  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {Mapper can be registered only once
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // It is important to add the type before parsing, so he will automatically try binding to parse mapper. If the type knows, it's okay,
        // Parse annotations in mapper.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if(! loadCompleted) { knownMappers.remove(type); }}}}Copy the code

The focus is on MapperProxyFactory


/ * * *@authorLasse Voss * LCnote Mapper proxy object creation factory */
public class MapperProxyFactory<T> {
  // The proxy interface is required, i.e., mapper
  private final Class<T> mapperInterface;
  
  // Save the cache to avoid repeating objects at new.
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface(a) {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache(a) {
    return methodCache;
  }
   // The new operation, nothing special, is simply created by calling the method that creates the proxy object.
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    returnnewInstance(mapperProxy); }}Copy the code

2. Create Mapper dynamic proxy

Add theInvocationHandlerWhat does it look like?

So the key points of MapperProxy, it’s a little bit too long, so I’m just going to pick the important ones, MapperProxy implements InvocationHandler, which is definitely a dynamic proxy. Mapper definitely uses reflection to associate XML files. The following method is not called when the new MapperProxyFactory is called, but when the MapperProxyFactory is called, the MapperProxyFactory is created.

At method invocation time, for the default method, the method is wrapped as MapperMethod, and then PlainMethodInvoker wraps the call.

/** waring, which is also important, is implemented when mapper calls InvocationHandler *@author Clinton Begin
 * @author Eduardo Macarron
 */
public class MapperProxy<T> implements InvocationHandler.Serializable {

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {// If it is an object method, call it directly
        return method.invoke(this, args);
      } else {
        returncachedInvoker(method).invoke(proxy, method, args, sqlSession); }}catch (Throwable t) {
      throwExceptionUtil.unwrapThrowable(t); }}private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      return MapUtil.computeIfAbsent(methodCache, method, m -> {
        if (m.isDefault()) {// If the method in the interface is default
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return newDefaultMethodInvoker(getMethodHandleJava9(method)); }}catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw newRuntimeException(e); }}else {
          //lcnote MapperMethod represents a mapper method. It contains the dataId corresponding to the method, the corresponding SQL type, and the specific signature information of the method, including the method return value, param parameter. Mapkey annotations
          return new PlainMethodInvoker(newMapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }}); }catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null? re : cause; }}// mapper method call interface,
  interface MapperMethodInvoker {
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;
   // This is just a utility class that implements mapper calls.
    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super(a);this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      returnmapperMethod.execute(sqlSession, args); }}}Copy the code

When is an object created?

Start with the getMapper method of the SqlSession. This is where the proxy object creation begins.

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // Call MapperProxyFactory to create a mapper instance.
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: "+ e, e); }}Copy the code

NewInstance creates a MapperProxy object. The MapperProxy object is described above.

As you can see, dynamic proxies are used to create a MapperProxy object.

What’s in MapperProxy?

SqlCommand

Represents the type of the associated Mapper and the SQL associated with this method

The MapperStatement is retrieved from the Configuration using the fully qualified class name of the Mapper. Assign id.

Determine what TYPE of SQL this method is.

MethodSignature

Represents the main information about a method.

    private final boolean returnsMany; 
    private final boolean returnsMap; // The return value is not a map, as long as mapKey is not null, this is true
    private final boolean returnsVoid; // Flag bit, whether no return value
    private final boolean returnsCursor; // Whether a Cursor is returned
    private final boolean returnsOptional;
    private finalClass<? > returnType;// The actual type returned by this method, such as List
      
       , is List
      
    private final String mapKey; // Mapkey is the value of the mapkey annotation, and we can write an article about the function of this mapkey
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final ParamNameResolver paramNameResolver;
Copy the code
MapperMethod

This object contains the above two objects, and when executed, the execute method is called.

Mapper is associated with XML, and the next step is to execute SQL. The following steps must include finding MapperStatement by fully qualified class name, processing input parameters, and processing dynamic SQL. OGNL is called here to parse. Then execute and process the results. The rest of the process comes later