[Mybatis Source Exploration]--- Interpretation of the Core Source of Mybatis Query Procedure--- mapper Call Method

Keywords: Mybatis Database Session Spring

Article Directory

1 Source Entry

The source entries for this article are as follows (see article " [Mybatis Source Exploration] - Beginning * Build a simple Mybatis framework>):

/***
 * mybatis Ways
 */
@Test
public void quickStart2() {
    // 2. Get sqlSession
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 3. Get the corresponding mapper
    TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
    // 4. Execute the query statement and return the result
    TUser user = mapper.selectByPrimaryKey(1);
    List<TUser> tUsers = mapper.selectListByIdList(Arrays.asList(1, 2, 3));
    //5. Close session
    sqlSession.close();
    log.info("user222:" + user);
    log.info("user222:" + tUsers);
}

In the [Mybatis Source Exploration] - Interpretation of the Core Source of Mybatis Query Process - Chat about the selectOne method first The sqlSessionFactory.openSession() method has been analyzed in this article. This article will start from getMapper(...) Method start.

2 sqlSession.getMapper(...) Method Core Source Interpretation

2.1 What should be analyzed without looking at the source code

Actually, don't look at getMapper(...) The bottom source of the method I think it's also possible to analyze what getMapper did, through the previous two articles, " [Mybatis Source Exploration]-Mybatis Configuration File Resolution Core Source Interpretation>,<[Mybatis Source Exploration] - Interpretation of the Core Source of Mybatis Query Process - Chat about the selectOne method first As you can see from its analysis:

(1) Configuration object is already encapsulated in sqlSession object - second blog
(2) The Configuration object has a MapperRegistry object, and the MapperRegistry object has a Map object, knownMappers, but the key of the Map object is not a String, but a class type, and the Value is a MapperProxyFactory object - > that is, a proxy factory object of that class type.- First Blog

The caller of the getMapper(...) method is the sqlSession object, and the parameter is a class type (the corresponding class type in the chestnut is TUserMapper.class): > It is natural to think of getMapper(...) The method will certainly get the corresponding proxy factory object for that class type, MapperProxyFactory, from the Configuration object.

Continuing with the analysis, we know that TUserMapper is an interface, and there is no specific implementation class for this interface in my project, but through mapper you can call the corresponding method of the TUserMapper interface, which means through getMapper(...) The mapper generated by the method must be a concrete implementation class of the TUserMapper interface - how does this work?It is believed that many people should know that dynamic proxy is used, but how is it implemented?Who is the person being represented (I think I read my previous two articles, " [Mybatis Source Exploration] - Interpretation of the Core Source of Mybatis Query Process - Chat about the selectOne method first "And" [Design Mode] - Decorator Mode, Static Agent Mode and Dynamic Agent Mode Can someone at least guess the true answer to this question - the SqlSession object?

2.2 [Source Code Analysis] Get the MapperProxyFactory object corresponding to TUserMapper

From the statement TUserMapper mapper = sqlSession.getMapper(TUserMapper.class); breakpoints in turn go into the following methods:

(1) getMapper method of sqlSession
Class DefaultSqlSession

@Override
public <T> T getMapper(Class<T> type) {
  //Calling the configuration getMapper method --- this must refer to the current sqlSession object itself
  return configuration.getMapper(type, this);
}

(2) configuration's getMapper method
Configuration of class

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //Call the getMapper method of mapperRegistry
  return mapperRegistry.getMapper(type, sqlSession); 
}

(3) getMapper method of mapperRegistry
MapperRegistry of the class

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  //Get the MapperProxyFactory object corresponding to T from knownMappers
  final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  if (mapperProxyFactory == null) {
    throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  }
  try {
  	//Calling the newInstance method of the mapperProxyFactory method generates a specific proxy object - the implementation class corresponding to the T interface
    return mapperProxyFactory.newInstance(sqlSession);
  } catch (Exception e) {
    throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  }
}

You can see that the source code, as I analyzed it, is the Configuration object obtained from SqlSession --->MapperRegistry object obtained from Configuration object --->MapperRegistry object obtained from MapperRegistry object --- knownMappers --->MapperProxyFactory object obtained from knownMappers that was put in at the start of the project (T in my chestnut is TUserMapper).

2.3 [Source Code Analysis] Use dynamic proxy mechanisms to generate and obtain proxy objects for TUserMapper

Continue tracking mapperProxyFactory.newInstance(sqlSession); the source code for the method, in turn, goes into the following methods:

2.3.1 Get the specific implementation class of InvocationHandler

Continuing to track the source of mapperProxyFactory.newInstance(sqlSession) will lead you to the following methods:
MapperProxyFactory of the class

public T newInstance(SqlSession sqlSession) {
  //This sentence is to generate the specific implementation class of InvocationHandler--it's easy to read here after writing the last article
  final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  //Generate dynamic proxy classes with InvocationHandler objects
  return newInstance(mapperProxy);
}

Let's first look at the construction of the MapperProxy object and its invoke() method:

  • Construction method of MapperProxy
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;//sqlSession object
    this.mapperInterface = mapperInterface; //Current Interface
    this.methodCache = methodCache;//Method Cache
  }
  • MapperProxy invoke() method
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  // ---------------------------------------------------------
  try {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    } else if (method.isDefault()) {
      if (privateLookupInMethod == null) {
        return invokeDefaultMethodJava8(proxy, method, args);
      } else {
        return invokeDefaultMethodJava9(proxy, method, args);
      }
    }
  } catch (Throwable t) {
    throw ExceptionUtil.unwrapThrowable(t);
  }
   // ---------------------------------------------------------
 
  //Building MapperMethod Objects
  final MapperMethod mapperMethod = cachedMapperMethod(method);
  //The execute method that executes the mapperMethod calls the real object and gets the returned result for return - the underlying call is methods such as SelectOne on the sqlSession object
  return mapperMethod.execute(sqlSession, args);
}

See here and then contact the previous article on dynamic proxy, you can definitely be 100% certain: the real proxy object generated is the SqlSession object.

2.3.2 Get specific proxy objects

As soon as the InvocationHandler object is found, it generates the proxy object, that is, one sentence. Continue tracking the source code of mapperMethod.execute(sqlSession, args); you can enter the following methods:

MapperProxyFactory of the class

protected T newInstance(MapperProxy<T> mapperProxy) {
  //Read the last article this sentence looks very simple
  //It is to create a proxy object with the class loader + current interface + InvocationHandler object of the current interface.
  //And strongly convert the created proxy object to the T type (corresponding to the chestnut in this article, which is a specific implementation class of the TUserMapper interface)
  return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

Note here: In the [Mybatis Source Exploration]-Mybatis Configuration File Resolution Core Source Interpretation As mentioned in the article, mapperInterface has been placed in the current MapperProxyFactory object during the process of Mybatis profile parsing.

2.4 A little insight

Understanding the principle of dynamic proxy and looking at the source code of Mybatis really makes it very simple

3 mapper.selectByPrimaryKey(1) Method Core Source Interpretation

From the above analysis and specific source tracking, you can see that the generated mapper is actually a proxy class, and the corresponding InvocationHandler of the proxy class is a MapperProxy object, which is described in the previous article, " [Design Mode] - Decorator Mode, Static Agent Mode and Dynamic Agent Mode An introduction to the principle of dynamic proxy shows that mapper can make method calls by way of method calls on the TUserMapper interface, but what it really calls is invoke(...) Method - invoke(...) Method - Source code 2.3.1.This is explained below.

3.1 cachedMapperMethod(method) method - Generating MapperMethod objects

final MapperMethod mapperMethod = cachedMapperMethod(method); the underlying source code of the method is not specific. The main work it does is to parse the mappedStatements attribute (select, insert, update, delete tags in mapper.xml) from the Configuration object with the current method and the corresponding interface of the current method into one appedStatement pair, while mappedStatementNts is a Map of these objects, see article " [Mybatis Source Exploration]-Mybatis Configuration File Resolution Core Source Interpretation ) Get the corresponding type of the current method - INSERT, DELETE, UPDATE or SELECT.

The results are as follows:
MapperProxy of the class

3.2 Underlying the mapper invocation -- essentially, using sqlSession objects to interact with the database

Follow up on the mapperMethod.execute(sqlSession, args) method with the following source code:
Class of MapperMethod

public Object execute(SqlSession sqlSession, Object[] args) {
  Object result;
  switch (command.getType()) {
    case INSERT: { //Invoke the insert method of sqlSession if the method type is "INSERT"
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.insert(command.getName(), param));
      break;
    }
    case UPDATE: { //Call sqlSession's UPDATE method if the method type is "UPDATE"
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
      break;
    }
    case DELETE: {//Call the DELETE method of sqlSession if the method type is "DELETE"
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
      break;
    }
    case SELECT: //Call sqlSession's SELECT method if the method type is "SELECT"
      if (method.returnsVoid() && method.hasResultHandler()) { //No return value
        executeWithResultHandler(sqlSession, args);
        result = null;
      } else if (method.returnsMany()) { //Multiple cases of return value
        result = executeForMany(sqlSession, args);
      } else if (method.returnsMap()) { //The case where the return value is Map
        result = executeForMap(sqlSession, args);
      } else if (method.returnsCursor()) { //The case where the return value is Cursor
        result = executeForCursor(sqlSession, args);
      } else { //The case where the return value is one
        Object param = method.convertArgsToSqlCommandParam(args);
        result = sqlSession.selectOne(command.getName(), param); //Call the selectOne method of sqlSession
        if (method.returnsOptional()
            && (result == null || !method.getReturnType().equals(result.getClass()))) {
          result = Optional.ofNullable(result);
        }
      }
      break;
    case FLUSH:
      result = sqlSession.flushStatements(); //Clear Current Session
      break;
    default:
      throw new BindingException("Unknown execution method for: " + command.getName());
  }
  if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
    throw new BindingException("Mapper method '" + command.getName()
        + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  }
  return result; //Return results
}

You can see that the underlying layer is really the method that corresponds to the sqlSession being invoked, that is, the way Mybatis uses mapper to invoke is actually an encapsulation of how ibatis interacts with the database using the sqlSession+method full qualified name+parameter.

4. Without summarizing, talk about recent planning

Several more recent articles:

(1) spring+Mybatis Integration Principle
(2) How Mybatis uses the spring transaction - this is a question my classmates ask me (:><:)
(3) A Mybatis problem we had on the eve of Double 11 - that was the original motivation I decided to clean up my Mybatis source code

189 original articles published. 187 praised. 390,000 visits+
Private letter follow

Posted by cpetercarter on Fri, 10 Jan 2020 17:37:17 -0800