The interviewer asked you Mapper agent of Mybatis, how much can you answer

Everyone knows my style and likes to bring stories into technical learning.

But... How can I bring in the source code with a story?

With my universal story template, Xiao Ming's treasure exploration journey.

That day, Xiao Ming came to the kingdom of Mybatis. He asked the old man at the gate that the gate was written iBatis. How could it be changed to Mybatis.

The old man replied: Oh, originally, it was iBatis, but now it has changed its name to Mybatis.

Xiao Ming scratched his head. The old man answered the question, but he didn't seem to answer.

Anyway, I know. Now it's Mybatis.

Xiao Ming knows that there is a magical thing in Mybatis Kingdom, called Mapper agent.

Due to his persistence in this object, Xiao Ming takes his weapon (IntelliJ IDEA) into the city to find out.

1, Curiosity

Curiosity is one of the essential factors driving our human development. It is said that Curiosity Kills cats. This sentence is not tenable in the field of technology. As a technician, we must be curious about things (I am talking about things in the field of technology. Don't be surprised by Uncle te's service, just go to Uncle TE)

You usually use Mybatis. Isn't there anything that makes you curious?

You create a Mapper interface and then write a Mapper.xml

Finally, you can add, delete, modify and query directly using Mapper interface.

Firstly, it is an interface. Secondly, how does it know what operations to perform?

I don't know whether you are curious or not. Anyway, I am very curious. It is because of this curiosity that I came to this adventure.

2, A rugged road

I've leveled the road for you. Curious students can follow my footprints. Maybe you can find a lot of wonderful things.

First of all, there must be a children's country. No, there must be a Mybatis kingdom.

pom.xml

After adding maven dependencies, remember to reload the project and ask him to download the corresponding jar package to the local warehouse.

<dependencies>
    <!--Mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.5</version>
    </dependency>
    <!--mysql drive-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
</dependencies>

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="TestMapper.xml"/>
    </mappers>
</configuration>

TestMapper.java

package dao;

import entity.TestEntity;
import java.util.List;
/**
 * @author Day and night programming of Muzi
 */
public interface TestMapper {
    List<TestEntity> list();
}

TestEntity.java

package entity;

import java.math.BigDecimal;
/**
 * @author Day and night programming of Muzi
 */
public class TestEntity {
    private  Long id;
    private String name;
    private BigDecimal salary;
    // getter setter 
}

TestMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.TestMapper">
    <!--Query all data-->
    <select id="list"  resultType="entity.TestEntity">
        select * from test
    </select>

</mapper>

Test.java

import dao.TestMapper;
import entity.TestEntity;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * @author Day and night programming of Muzi
 */
public class Test {
    public static void main(String[] args) throws IOException {
        // 1. mybatis configuration file
        String resource = "mybatis-config.xml";
        // 2. Get input stream
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 3. Create SqlSessionFactory factory. Mapper's dynamic proxy operation will be performed
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        // 4. Create SqlSession
        try (SqlSession session = sqlSessionFactory.openSession()) {
            // 5. Obtain Mapper through sesson. This Mapper will program the proxy Mapper of Mybatis
            TestMapper mapper = session.getMapper(TestMapper.class);
            // 6. Call method
            List<TestEntity> list = mapper.list();
            System.out.println(list);

        }
    }
}

After my exploration, the main logic is completed in the third step. How do you say that? Steal heaven and change the day.

We wrote in the previous article: reflection, dynamic agent, factory mode

Here we use these 2 technologies to package our Mapper. You think you use your own Mapper, but what you think is right? It's wrong.

1. 
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
2. 
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
3. 
super(new Configuration());
One attribute is:
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);

A simple class diagram is drawn. In fact, a MapperRegistry is created when reading the configuration file

The MapperRegistry is where the treasure (the proxy of the interface Mapper we wrote) is stored.

We can see when MapperRegistry comes out

Let's see when to use his addMapper method, and of course, the addMappers method

Let's focus on the addMapper method analysis. Don't be too persistent

1. It has been created above parser
parser.parse()
2. You can see that the configuration file is being parsed here configuration Is the root node of our configuration file
parseConfiguration(parser.evalNode("/configuration"));
3. see Mapper If so, look at this analysis mappers Other labels can be ignored first
mapperElement(root.evalNode("mappers"));

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
        // Start looping through the sub tags of mappers. Its sub tags can make mappers and package s
        for (XNode child : parent.getChildren()) {
            // If it's a package operation, we don't look at this
            if ("package".equals(child.getName())) {
                String mapperPackage = child.getStringAttribute("name");
                configuration.addMappers(mapperPackage);
            } else {
                // Let's look at this to get the properties of the word tag
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    // If only resource is configured, we only see this way, because this is what we configure
                    // Read resources
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    // Convert to XMLMapperBuilder
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    // Walk analysis
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    // If only url is configured
                    ErrorContext.instance().resource(url);
                    InputStream inputStream = Resources.getUrlAsStream(url);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url == null && mapperClass != null) {
                    // If only class is configured
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                } else {
                    // A mapper can only have one attribute, or url, or resource, or class
                    // If you report this error when developing, you should configure multiple properties
                    // After my verification, just trust me
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
        }
    }
}
XMLMapperBuilder.java
public void parse() {
    // Judge whether the file has been parsed. All parsed files are placed in a set
    if (!configuration.isResourceLoaded(resource)) {
        // After a series of operations
        configurationElement(parser.evalNode("/mapper"));
        // Put into set and mark as resolved resources
        configuration.addLoadedResource(resource);
        // Start binding Mapper and Mapper.xml
        bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
}

private void bindMapperForNamespace() {
    // Get namespace dao.TestMapper
    String namespace = builderAssistant.getCurrentNamespace();
    // If the namespace is not configured, Mapepr and Mapper.xml will not be bound
    // If the namespace is empty, the preceding parsing will directly report an exception. I don't know what can go here
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // Get the class TestMapper.class corresponding to the namespace
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
          // If you can't find the class, forget it, because it's not necessary
          // We can also refer to this method in our own business
          // if class has a bala operation
          // else does not operate 
      }
      if (boundType != null) {
        // First, judge whether this class has been included. In fact, it is the hasMapper of mapperRegistry called
        if (!configuration.hasMapper(boundType)) {
          // This is to adapt to spring. A flag is set to prevent multiple loading of this resource
          // See MapperAnnotationBuilder#loadXmlResource for more information
          configuration.addLoadedResource("namespace:" + namespace);
          // We don't pay attention to those, we only pay attention to this 
          configuration.addMapper(boundType);
        }
      }
    }
  }

As we said before, look at the code, especially the source code. Don't go into the black hole. You must be clear about your purpose this time.

It's like I click configuration.addLoadedResource("namespace:" + namespace); Here you know that it's just to make a mark for the adaptation of Spring. As for why and how to do the adaptation, you don't have to worry. Your purpose this time should be very clear. You just want to explore how Mapper proxy is represented, so don't get involved.

Configuration.java
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
}

public class MapperRegistry {
  // This is a reference to Configuration, because some configurations will be used during registration
  private final Configuration config;
  // Finally, the treasure chest will be stored in the proxy factory here. Our last code call getMapper uses this factory to create one for us
  // Proxy object: as we wrote in the previous articles, the reflection, proxy mode and factory mode fit here very well
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public MapperRegistry(Configuration config) {
    this.config = config;
  }

  // This method is generally called to get the proxy code
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    // If there is no factory corresponding to Mapper type, throw an exception
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // Create a proxy object 
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }
    
  // Judge whether the containsKey of hashMap exists
  public <T> boolean hasMapper(Class<T> type) {
    return knownMappers.containsKey(type);
  }
  
  // Register Mapper
  public <T> void addMapper(Class<T> type) {
    // Judge whether it is an interface type. If it is not an interface type, it will not be processed
    if (type.isInterface()) {
      // If it already exists, no throwing exception will be added 
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      // Whether the tag is loaded successfully
      boolean loadCompleted = false;
      try {
        // Preemption is very important
         // If it is not occupied, it may be tried to bind automatically  
        // If the type already exists, it will not try the judgment above. hasMapper is judging this
        // I didn't quite understand it, but it doesn't matter. It's better to write like this
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // Parse some initialization
        parser.parse();
        loadCompleted = true;
      } finally {
        // If it is not loaded successfully, it is removed from the map
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
  
}


Everyone who read my article yesterday knows that next, let's take a look at the matchmaker MapperProxyFactory

// The matchmaker is responsible for introducing the object and creating the factory class of our Mapper interface proxy
public class MapperProxyFactory<T> {
  // Class object of interface
  private final Class<T> mapperInterface;
  // Method object and encapsulation of method object
  private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
  // Constructor
  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }
  
  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethod> getMethodCache() {
    return methodCache;
  }
  //Create proxy object
  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    // Create a Proxy class and return it. For this Proxy, see my previous article on dynamic Proxy  
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(),
                                      new Class[] { mapperInterface },
                                      mapperProxy);
  }
  // Here is to pass in an sql session and create a Mapper interface proxy class
  public T newInstance(SqlSession sqlSession) {
    // Here you create Mapper's proxy, which implements invocationhandler (see my previous dynamic proxy articles)  
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession,
                                                          mapperInterface,
                                                          methodCache);
    return newInstance(mapperProxy);
  }

}

MapperProxy.java

/**
 *    Copyright 2009-2017 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.binding;

import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

import org.apache.ibatis.lang.UsesJava7;
import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession;

// Both JKD dynamic agents need to implement InvocationHandler
// The specific proxy is done in invoke
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -6424540398559729838L;
  // sqlSession
  private final SqlSession sqlSession;
  // Interface object type TestMapper
  private final Class<T> mapperInterface;
  // Method list in interface, etc 
  private final Map<Method, MapperMethod> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  // All methods of the interface proxy object will be called here 
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // Judge whether it is the basic method toString hashCode. If so, call it directly without proxy
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else if (isDefaultMethod(method)) {
        // The method to determine whether the default is modified. If yes, it will be handled specially  
        return invokeDefaultMethod(proxy, method, args);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
    // We usually come here 
    // If there is a cache, take the cached data. If there is no cache, create the data and put it into the cache 
    // Friends can see that Map is a magical existence. It exists everywhere
    // Therefore, you must prepare relevant knowledge for the interview
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

  private MapperMethod cachedMapperMethod(Method method) {
    // First judge whether there is  
    MapperMethod mapperMethod = methodCache.get(method);
    // No,   
    if (mapperMethod == null) {
      // establish 
      mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
      // Put in cache
      methodCache.put(method, mapperMethod);
    }
    // return
    return mapperMethod;
  }
}


MapperMethod.java

package org.apache.ibatis.binding;

import org.apache.ibatis.annotations.Flush;
import org.apache.ibatis.annotations.MapKey;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ParamNameResolver;
import org.apache.ibatis.reflection.TypeParameterResolver;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

// This class is powerful. This is the core class. Here we encapsulate our operations using SqlSession
public class MapperMethod {
  // Type of Sql tag Insert Update Delete Select
  private final SqlCommand command;
  // Method parameter information, return information, etc  
  private final MethodSignature method;
  // structure
  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
  // Here are a series of methods encapsulating SqlSession, such as selectOne, select, insert, delete, etc
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      // Why do we often say that the three tags of Insert update delete actually have the same function
      // Usually there are only semantic differences 
      // We can click the sqlSession source code to see that the update method is called in the end
      case INSERT: {
        // Processing parameters
    	Object param = method.convertArgsToSqlCommandParam(args);
        // Call sqlSessioninsert
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      // If it's Select, that's more    
      case SELECT:
        // If the return type is void and there is a custom ResultHandler
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
            // Return type multiline
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
            // Fanhu Map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
            // Return to Cursor
          result = executeForCursor(sqlSession, args);
        } else {
          // Return single  
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
        }
        break;
      case FLUSH:
          // wipe cache   
        result = sqlSession.flushStatements();
        break;
      default:
        // This usually doesn't appear unless you're a fool, ha ha    
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    // It's interesting here
    // We may have encountered that if the query result is the basic type (boolean, char, byte, short, int, long, float, double), it is easy to report null exceptions
    // When we write code, we must pay attention to the basic type. We must ensure that there is a return value, otherwise you will use the encapsulated type Integer Double, etc
    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 results  
    return result;
  }
  
  // insert update delete return processing rowCount is the number of affected rows returned after Sqlsession execution
  private Object rowCountResult(int rowCount) {
    final Object result;
    // If the return type is void, it will directly return null  
    if (method.returnsVoid()) {
      result = null;
        // Return type Integer int
    } else if (Integer.class.equals(method.getReturnType()) || Integer.TYPE.equals(method.getReturnType())) {
      result = rowCount;
        // Return type Long long
    } else if (Long.class.equals(method.getReturnType()) || Long.TYPE.equals(method.getReturnType())) {
      result = (long)rowCount;
        // Return type Boolean
    } else if (Boolean.class.equals(method.getReturnType()) || Boolean.TYPE.equals(method.getReturnType())) {
      result = rowCount > 0;
    } else {
        // Throw exceptions directly for other return types
      throw new BindingException("Mapper method '" + command.getName() + "' has an unsupported return type: " + method.getReturnType());
    }
    return result;
  }
  
  // There is a custom ResuleHandler
  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    // 
    if (void.class.equals(ms.getResultMaps().get(0).getType())) {
      throw new BindingException("method " + command.getName() 
          + " needs either a @ResultMap annotation, a @ResultType annotation," 
          + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    // 
    Object param = method.convertArgsToSqlCommandParam(args);
    // 
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      // 
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
  }
  // Multiple returned results
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    // Return value  
    List<E> result;
    // Convert parameters to ParamMap  
    Object param = method.convertArgsToSqlCommandParam(args);
    // Whether there are paging parameters
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
      // Execute the selectList of sqlSession directly
      result = sqlSession.<E>selectList(command.getName(), param);
    }
    // class1.isAssignableFrom(class2)
    // Determine whether class2 is a subclass or sub interface of class1
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      // If the return type is Array, convert to Array  
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        // No, convert to declared collection
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }
  // Return to Cursor
  private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
    Cursor<T> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.<T>selectCursor(command.getName(), param);
    }
    return result;
  }
  // Convert to collection
  private <E> Object convertToDeclaredCollection(Configuration config, List<E> list) {
    // First create an object of the declared collection type  
    Object collection = config.getObjectFactory().create(method.getReturnType());
    // Convert to proxy
    MetaObject metaObject = config.newMetaObject(collection);
    // Put all the elements in
    metaObject.addAll(list);
    return collection;
  }
  // Convert to array
  @SuppressWarnings("unchecked")
  private <E> Object convertToArray(List<E> list) {
    // Create array object
    Class<?> arrayComponentType = method.getReturnType().getComponentType();
    Object array = Array.newInstance(arrayComponentType, list.size());
    // Judge whether it is the basic type array int [] long []
    if (arrayComponentType.isPrimitive()) {
      // If it is a base type, a conversion is required
      for (int i = 0; i < list.size(); i++) {
        // 
        Array.set(array, i, list.get(i));
      }
      return array;
    } else {
      // If you do not call toArray directly, convert to Array
      return list.toArray((E[])array);
    }
  }
  // Return to Map
  private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
    Map<K, V> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
    } else {
      result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
    }
    return result;
  }
  
  // Custom Map 
  // We can also refer to this method in our business, that is, we rewrite the get method and throw an exception if we don't get the element
  public static class ParamMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -2212268410512043556L;

    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + keySet());
      }
      return super.get(key);
    }
  }
}


//  Encapsulates the specific actions to be performed
public static class SqlCommand {
    // xml id, such as: list
    private final String name;
    // Insert, update, delete, etc
    private final SqlCommandType type;

    public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
      // Name list
      final String methodName = method.getName();
      // Class dao.TestMapper 
      final Class<?> declaringClass = method.getDeclaringClass();
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        // Is there a Flush label  
        if (method.getAnnotation(Flush.class) != null) {
          name = null;
          // Set the type to Flush
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "." + methodName);
        }
      } else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        // INSERT, UPDATE, DELETE, SELECT, FLUSH exceptions are thrown directly when the type is not recognized;
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: " + name);
        }
      }
    }

    public String getName() {
      return name;
    }

    public SqlCommandType getType() {
      return type;
    }

    private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
        Class<?> declaringClass, Configuration configuration) {
      // statementId ==> dao.TestMapper.list
      String statementId = mapperInterface.getName() + "." + methodName;
      // If there is already a direct return
      if (configuration.hasStatement(statementId)) {
        return configuration.getMappedStatement(statementId);
      } else if (mapperInterface.equals(declaringClass)) {
        return null;
      }
      for (Class<?> superInterface : mapperInterface.getInterfaces()) {
        if (declaringClass.isAssignableFrom(superInterface)) {
          MappedStatement ms = resolveMappedStatement(superInterface, methodName,
              declaringClass, configuration);
          if (ms != null) {
            return ms;
          }
        }
      }
      return null;
    }
  }

  public static class MethodSignature {
    // Return multiple results
    private final boolean returnsMany;
    // Return Map
    private final boolean returnsMap;
    // Return void
    private final boolean returnsVoid;
    //Return Cursor
    private final boolean returnsCursor;
    // Return type
    private final Class<?> returnType;
    // mapKey
    private final String mapKey;
    // The location of the resultHandler type parameter
    private final Integer resultHandlerIndex;
    // The location of the rowBound type parameter
    private final Integer rowBoundsIndex;
    // Parameter processor
    private final ParamNameResolver paramNameResolver;
  }

3, Nagging

The final result of the expedition was that I was blindfolded. Looking at it, I was discouraged.

This architecture is awesome, all kinds of packages, all kinds of modes, all kinds of

The most important thing is that we see that we use the proxy mode and factory mode to proxy our Mapper interface,

The interface we get through getMapper is actually a proxy object. At this time, all operations are performed through the proxy MapperProxy

The Jdk dynamic proxy by InvocationHandler is implemented

I've seen the source code for the second time, but it's still not so clear, so we don't need to master all the points at once. First, we need to master a small point, such as how to realize the Mapper interface agent through dynamic agent, and then talk about the specific contents of other agents slowly.

Welcome to the official account: Muzi's day and night programming

Added by Catfish on Sun, 31 Oct 2021 21:44:11 +0200