How is mapper associated with XMl in Mybatis

How is mapper associated with XMl in Mybatis

From the source code analysis, we all know through Mybatis that nameSpace must be specified as the fully qualified class name of mapper. So we can relate. Mapper's implementation must be dynamic dialing, which is enhanced in InvocationHandler. Here's how to do it?
In the following analysis, the source code looks boring and involves a lot of things. There are many things designed during analysis, which is easy to deviate. I try to get back to the subject.

1. XML file parsing

The xml parsing here is cumbersome. If you analyze it line by line, there are many, many, many, so here's the main line to analyze. After that, it will be analyzed in blocks and topics.

Parsing the total configuration file

If you start by creating SqlSessionFactory in the classic Mybatis, you can certainly see the following code

Some comments in the code were written when I looked at the source code, and some were outrageous. Some record my confusion when I read it before. Then I understood it when I read it. So keep it here.

  private void parseConfiguration(XNode root) {
    try {
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties")); //Parse the properties tag and put it in the Variables of parser and config
      Properties settings = settingsAsProperties(root.evalNode("settings"));//Load setting tag
      loadCustomVfs(settings); //lcnote what is vfs here? How? I know this
      //I now know what his vfs is. vfs (virtual file system) abstracts several APIs through which you can access resources on the file system; Like in
      // When parsing mapperElement(root.evalNode("mappers"); If you specify package, you can obtain all class files under the package path through VFS.
      // It will be added to the map, which is similar to the classpathsacner in spring. You can specify the filter.


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

      //From here on, we all parse the specific tag, new out the object, and set the properties under the tag,
      // From the analysis here, we can basically see several important points in mybatis. The first is objectfactory, objectfactory. objectFactory´╝îplugins
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectFactory"));
      reflectorFactoryElement(root.evalNode("objectFactory"));

      //Here you will 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 here is the focus, parsing mapper files,
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

Directly see how to parse the mapper file.

As you can see from this code, two tags can be written under the mappers tag. package and mapper tags. There are different analytical methods for the two.

Parse package label

Here is only part of the source code intercepted. Several source codes will also be spliced together for easy viewing

 if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        }
//The following is the method of configuration.addMappers(mapperPackage)
public void addMappers(String packageName) {
  //mapperRegistry is a register that registers mappers and maintains many objects composed of all mappers.
    mapperRegistry.addMappers(packageName);
  }
//The following 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 found is a subclass of this class.

  public void addMappers(String packageName, Class<?> superType) 
  {
    //resolverUtil is a tool class that finds a collection of subclasses of a specified class under a specified package.
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    
    //Find the appropriate Class and add it to the 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 a given package (including sub paths). Call the Test method to match, and the matched class can be obtained by calling getClasses.)
  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 internal 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());
    }
  }


//****************************Key points***********************************
//   The public < T > void addmapper (class < T > type) method instantiates the appropriate class found above and loads it into mapperRegistry.
// And this method is in mapperRegistry.
  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {//mapper can only be registered once
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        
        // Use mapper new to get MapperProxyFactory and put it in 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.
        // This is very important to add types before parsing, so he will automatically try to bind the parsing mapper. If the type knows, it's nothing,
        // Here I think it is to parse the annotations in mapper.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

There are several important classes

  1. MapperRegistry: the register of mappers, which saves all mappers.
  2. Configuration: the configuration object reports an error for all the data that can be used during the operation of Mybatis.
  3. MapperProxyFactory: the creation factory of proxy mapper. There is nothing special in it, that is, calling the method of creating proxy objects to create objects.
  4. MapperAnnotationBuilder: parse the annotations in mapper. These annotations have the same function as XMl, but they are not recommended.
Parsing mapper Tags

I feel like this when I look at the source code. Wow, it can be used in this way. This framework has this function.

{		

          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, that is, the source of resource is different.
            
            
            // Here, the resource will be loaded, the mapper file will be parsed, and the maperstatement object will be built,
            try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              mapperParser.parse();//The parsing operation of lcnote here is the same as that of configuration file. All are building XMLMapperBuilder, then calling parse method.
            }
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            try(InputStream inputStream = Resources.getUrlAsStream(url)){
              XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
              mapperParser.parse();
            }
          } else if (resource == null && url == null && mapperClass != null) {
            
            //There is nothing special here, that is, the process of parsing the package tag and loading after obtaining 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.");
          }
        }

It can be seen from here that three attributes, resource, url and class, are supported, and the loading order is resource first, url and class, and the three cannot be specified at the same time.

As can be seen from the above, the loading operations of resource and url are consistent, that is, the source of resource is different. The loading of class is consistent with the loading process after the package tag is parsed and the mapper is obtained. Here, you can directly see XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); Here we go

Building XMLMapperBuilder

Here I want to talk about BaseBuilder. This class is a very basic class in Mybatis. Many analyses are inherited from him before they begin to do analysis.

A little note. MapperBuilderAssistant is really a tool class. Let's take a look at its structure first

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

This class corresponds to all the things generated when parsing a mapper file. For example, resultMap, sql, select, update, and so on. These related things. Will be added to the BaseBuilder through this object.

XMLMapperBuilder inherits from BaseBuilder. XMLMapperBuilder is mainly 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;
  }

//Construction method of super
 public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();//Get the typeAliases label from the configuration file
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();//Get typeHandlers from the configuration file
  }

Constructors have nothing to say. The point is the following Parse method

Several important classes appear above

  1. Xmlmapterentityresolver: the entity parser of xmlMapper. Inherits from EntityResolver. It is an object in the org.xml.sax package.
  2. BaseBuilder: the base class for all xml parsing.
Call the parse method of xmlmaperbuilder
  public void parse() {
     //The loaded resource collection is saved in the configuration. Let's judge it first
    if (!configuration.isResourceLoaded(resource)) { //I will go to a set in the configuration to find it
      
      // Here is the key point. The key point is to parse the mapper tag
      configurationElement(parser.evalNode("/mapper"));//Parsing mapper Tags
      configuration.addLoadedResource(resource);//Add to already loaded collection
      bindMapperForNamespace(); //Try loading the configuration file through nameSpace.
      //Note that this is an attempt, and the nameSpace does not have to be consistent with the Mapper interface.
    }
    
    //The following operation is also very interesting.
    //When parsing xml, if an error (IncompleteElementException) is reported, it will not be thrown immediately, but these error reports will be cached. After the above parsing is completed, try it.
    // 
    parsePendingResultMaps(); 
    parsePendingCacheRefs(); 
    parsePendingStatements(); 
  }

Here is mainly to parse the mapper tag and try to load the corresponding mapper through nameSpace. If it is loaded, the MapperRegistry above will be called to register the mapper in it.

The parsing operation here is very similar to the previous operation of parsing the configuration tag. First parse the parent tag and then the child tag. Let's see how to parse it

private void configurationElement(XNode context) {
    try {
      // Parse 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 label element
      // Parse cache tag
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //Parsing sql
      sqlElement(context.evalNodes("/mapper/sql"));
      
      //Warning is very important here. We really start to parse the select, insert, update and 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);
    }
  }

explain

  1. Parse the ParameterMap. After the above parsing, build the ParameterMapping and add it to the builder assistant. In the builder assistant, it will be converted into ParameterMap and finally added to the parameterMaps property of the Configuration object. This Configuration is global. And a mapper can have multiple ParameterMap tags.

  2. Parse the cache, get the value of the corresponding attribute element, build the cache object, add it to the configuration, and assign the currentCache in builder assistant as the current cache object. And a Mapper can only have one cache tag.

  3. Parsing the resultMap is more complicated than the previous two. There are many tags under the resultMap.

     for (XNode resultChild : resultChildren) {
          if ("constructor".equals(resultChild.getName())) {
            //This is very simple. Set the parameters through the construction method
            processConstructorElement(resultChild, typeClass, resultMappings);
          } else if ("discriminator".equals(resultChild.getName())) {
            // lcnote corresponds to the Discriminator object. In reality, the Discriminator tag has not been used. After that, it seems that this tag can realize the function of swtich case and can be combined with resultMap
            // To do something interesting. This has really 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));
          }
        }
    
    • For the constructor tag, the build ResultMapping object is added to the ResultMapping collection.
    • I haven't used the discriminator tag. I'll write it later. Build the discriminator object.
    • For other tags, build a ResultMapping object and add it to the ResultMapping collection.

    These are all things under a resultmap tag. After parsing a resultmap, the above related objects will be assembled into a ResultMapResolver and call resultMapResolver.resolve(); Method, build a resultmap object and put it in 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) in the sqlFragments of the XMLMapperBuilder object. sqlFragments is a StrictMap inheritance and HashMap. It rewrites the put and get methods, mainly adding judgment during put and get. sqlFragments store sql fragments. Note that the dynamic tags in sql are not processed when parsing here. You should know that the dynamic label is determined according to the parameters. Here's just a simple way to save him. And sql tags are multiple.

    2. Resolve the select Insert update delete tag. That's the point. For clarity, let's look at the source code. There are multiple select|insert|update|delete tags. So here is loop parsing. The following code is only the parsing operation in the loop body.

        public void parseStatementNode() {
          String id = context.getStringAttribute("id");
          String databaseId = context.getStringAttribute("databaseId");
          //You know, you can specify the dataBase in mybatis, and you can also specify the databaseid to be applied in the tag.
          // Here is a judgment. If it is not the current application, it will not be parsed..
          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 is of type select, the default is false.
          boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
          //If useCache is not specified and is of type select, the default value is true.
          boolean useCache = context.getBooleanAttribute("useCache", isSelect);
          
          // What does this label mean? I haven't used it. It is recommended to check the official documentation of mybatis.
          boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
      
          // Include Fragments before parsing
          // Before parsing sql, you want to parse the include tag. You can tell by the name. This is used to handle < include > tags.
          // This explains why it was so simple when parsing sql tags before. The sql tag is ultimately used in the Statement.
          // Dynamic sql should also be written in the Statement, so it should be included before actually parsing the tag. Put one on
          // In the subsequent parsing operation. A piece of analysis
          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.
          // Parse selectkey
          processSelectKeyNodes(id, parameterTypeClass, langDriver);
          //lcnote parsing sql selectKey has been remove d before parsing
          // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
          
          
          
          //Here you can determine whether you need to use usegenerated keys, and a cache is maintained. You can see that id is the id of 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 tell you the truth, I really don't know what the langDriver of Mybatis is. I'll analyze it later
          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");
       
          
          //As you can see here, the assembled MappedStatement must be added to the database through builder assistant
          // The map of the statement is maintained in the configuration. The key is the id of namespace+mapper  
          builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
              fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
              resultSetTypeEnum, flushCache, useCache, resultOrdered,
              keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
        }
      

Calling the parse method of XMLMapperBuilder will parse the Mapper.xml file

  1. First of all, you should know that the data range composed of each xml element must have a corresponding object in Mybatis.
  2. When dealing with xml, we must use alias and type processor. These two are common. I'll see you later.
  3. Add the parsed objects to the configuration.
  4. It is interesting to parse mapper files. For example, ${} can be used to reference environment variables in the sql tag. Nested references are also supported.
  5. Indeed, I found this usage when looking at the source code. Some things have not been used. Let's take a look at it 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 when all xml files are parsed.

Try binding mapper via nameSpace

  private void bindMapperForNamespace() {
     // As mentioned earlier, builder assistant corresponds to a tool class during mapper parsing. Get namespace
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        //Attempt 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);
      }
    }
  }

It's clear here that when parsing xml files, corresponding tags will be generated, and then they will be added to the configuration. Then, the class class will be loaded through nameSpace. If nameSpace wants to correspond to Mapper, it must be the same. If it doesn't need to correspond, it's all right. Just write it.

Add the loaded class to the configuration. A map is maintained in the configuration. The key is class and the value is MapperProxyFactory. Note configuration.addMapper(boundType); method. Now we'll look at this method.

Cache through the nameSpace in the mapper tag. And generate proxy objects to create factories.

This method is in MapperRegistry.

  public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {//mapper can only be registered 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.
        // This is very important to add types before parsing, so he will automatically try to bind the parsing mapper. If the type knows, it's nothing,
        // Parse the annotations in mapper.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

The focus is MapperProxyFactory

/**
 * @author Lasse Voss
 * lcnote mapper Proxy object creation factory
 */
public class MapperProxyFactory<T> {
  // An interface that requires a proxy, that is, mapper
  private final Class<T> mapperInterface;
  
  //Save the cache to avoid duplicate objects at new.
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

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

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

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }
   // This new operation, without any special, simply calls the method to create 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);
    return newInstance(mapperProxy);
  }
}

2. Mapper dynamic proxy creation

What does the added InvocationHandler look like?

The following is the key MapperProxy in the focus. It's too long here. I'll pick the important one. MapperProxy implements InvocationHandler, which must be a dynamic proxy. mapper must use reflection to associate with xml files. The following method is not called when new MapperProxyFactory is called. It will be created through MapperProxyFactory when calling. Here, follow the above.

When calling a method, for the default method, encapsulate the method into MapperMethod, and then wrap the call with PlainMethodInvoker.

/** waring´╝î This is also important. The InvocationHandler is implemented when the mapper is called
 * @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 a method in the object class, just call it directly
        return method.invoke(this, args);
      } else {
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.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 new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          //Lcnote mappermethod represents a mapper method. It includes the dataId corresponding to the method, the corresponding sql type, and the specific signature information of the method, including the method return value and param parameters. mapkey annotation
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }


  // mapper method calls the 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 tool class that implements mapper calls
    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      return mapperMethod.execute(sqlSession, args);
    }
  }
}

When to create an object?

Start with the getMapper method of SqlSession. The proxy object is created from here.

  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);
    }
  }

newInstance will create a MapperProxy object. MapperProxy objects are described above.

It can be seen that a MapperProxy object is created by using dynamic proxy.

What's in MapperProxy?

SqlCommand

Represents the associated mapper and the type of sql associated with this method

MapperStatement will be found from Configuration through Mapper's fully qualified class name. Assignment id.

Determine which sql type this method is.

MethodSignature

Represents the main information of a method.

    private final boolean returnsMany; 
    private final boolean returnsMap; //Whether the return value is a map is true as long as the mapKey is not null
    private final boolean returnsVoid; //Flag bit, whether there is no return value
    private final boolean returnsCursor; //Did you return a Cursor
    private final boolean returnsOptional;
    private final Class<?> returnType; //The real return type of this method, such as List < student >, is List
    private final String mapKey; //Mapkey is the value in the mapkey annotation. You can write an article to analyze the function of this mapkey later
    private final Integer resultHandlerIndex;
    private final Integer rowBoundsIndex;
    private final ParamNameResolver paramNameResolver;
MapperMethod

This object contains the above two objects. When it is actually executed, the execute method will be called.

Mapper is associated with XML. sql will be executed next. The following steps must be to find MapperStatement through fully qualified class name, process input parameters and process dynamic sql. OGNL will be called to parse. Then execute and process the results. The following process will be described later.

Tags: Java Mybatis

Posted on Sat, 04 Sep 2021 20:20:09 -0400 by Grunt