This article source code from mybatis-spring-boot-starter 2.1.2 version

The previous chapters have introduced the creation of MapperPoxy, the generation of MapperStatement, Executor and other core components, which are actually paving the way for this article. In this article we will look at how Mybatis executes our defined SQL statement. It’s a little long, so LET me show you a picture.

Sequence diagram

Environment to prepare

    <! --mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.2</version>
    </dependency>
    <! - mysql driver - >
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <scope>runtime</scope>
    </dependency>
     <! -- Database connection pool -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.26</version>
    </dependency>
Copy the code

Write Dao and corresponding Mapper:

public interface RoleMapper {
    @Select("select * from role")
    @Results({
            @Result(property = "roleId",column = "role_id"),
            @Result(property = "roleName",column = "role_name")})List<Role> selectALl(a);
}
Copy the code

Write unit tests, then we use the test method on the line of MybatisSql statement execution of the exploration.

@RunWith(SpringRunner.class)
@SpringBootTest
public class MybatisApplicationTests {

    @Resource
    private RoleMapper roleMapper ;

    @Test
    public void query2(a){ List<Role> roles = roleMapper.selectALl(); }}Copy the code

MapperProxy

When we give the Mapper class to Spring to manage, we actually store MapperProxy, Mapper’s proxy object, in the Spring container. When a proxy object is used to invoke a method in our own interface, the Invoke () method of the InvocationHandler implementation class is executed. Mybatis invoke() invoke(); Mybatis invoke();

  @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 {
        returncachedInvoker(method).invoke(proxy, method, args, sqlSession); }}catch (Throwable t) {
      throwExceptionUtil.unwrapThrowable(t); }}Copy the code

CachedInvoker (method).invoke(Proxy, Method, args, sqlSession) Here we see that the final query result is actually cachedInvoker(method) executing the invoke method. Obviously cachedInvoker(Method) is also a proxy object, so let’s take a look at it.

2.1 invoke

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
        // The mapping proxy object holds our query method cache and returns the same method the second time.
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return newDefaultMethodInvoker(getMethodHandleJava9(method)); }}catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw newRuntimeException(e); }}else {
          return new PlainMethodInvoker(newMapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }}); }catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null? re : cause; }}Copy the code

First call methodCache.com puteIfAbsent this method, this is a proxy object mapping method our query cache, we call for the first time is certainly no cache, Will enter the new PlainMethodInvoker (new MapperMethod (mapperInterface, method, sqlSession getConfiguration ())). There’s a new MapperMethod object as a PlainMethodInvoker constructor, which we’ll talk about in a minute, but we’ll keep going. PlainMethodInvoker is an inner class of MapperProxy:

  private static class PlainMethodInvoker implements MapperMethodInvoker {
   private final MapperMethod mapperMethod;

   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

PlainMethodInvoker defines the member property mapperMethod and overwrites the invoke() method. So we’re going to end up with the execute() method of MapperMethod. Let’s see what a MapperMethod is.

MapperMethod

Let’s start with MapperMethod construction:

public MapperMethod(Class
        mapperInterface, Method method, Configuration config) {
    this.command = new SqlCommand(config, mapperInterface, method);
    this.method = new MethodSignature(config, mapperInterface, method);
  }
Copy the code

MapperMethod has two command and method attributes, one of which is related to SQL and the other is related to its own methods.

3.1 SqlCommand

SqlCommand as we mentioned earlier, it will get the MappedStatement in the Configuration based on the Mapper interface name and method name.

 public SqlCommand(Configuration configuration, Class
        mapperInterface, Method method) {
        // Get the method name
      final String methodName = method.getName();
      finalClass<? > declaringClass = method.getDeclaringClass();// Get the MappedStatement interface name + method name
      MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
          configuration);
      if (ms == null) {
        if(method.getAnnotation(Flush.class) ! =null) {
          name = null;
          type = SqlCommandType.FLUSH;
        } else {
          throw new BindingException("Invalid bound statement (not found): "
              + mapperInterface.getName() + "."+ methodName); }}else {
        name = ms.getId();
        type = ms.getSqlCommandType();
        if (type == SqlCommandType.UNKNOWN) {
          throw new BindingException("Unknown execution method for: "+ name); }}}Copy the code

3.2 MethodSignature

MethodSignature does some processing on method entry and exit parameters, such as setting the token based on the method return type, input parameter names, etc., in method, which is used later in ResultSetHandler.

 public MethodSignature(Configuration configuration, Class
        mapperInterface, Method method) {
      // Parse the method return type
      Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
      // Return value type
      if (resolvedReturnType instanceofClass<? >) {this.returnType = (Class<? >) resolvedReturnType; }else if (resolvedReturnType instanceof ParameterizedType) {
        this.returnType = (Class<? >) ((ParameterizedType) resolvedReturnType).getRawType(); }else {
        this.returnType = method.getReturnType();
      }
      this.returnsVoid = void.class.equals(this.returnType);
       // Whether to return multi-tune results
      this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
      this.returnsCursor = Cursor.class.equals(this.returnType);
      this.returnsOptional = Optional.class.equals(this.returnType);
      this.mapKey = getMapKey(method);
      // Whether the return value is MAP
      this.returnsMap = this.mapKey ! =null;
      this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
      //resultHandler type parameter location
      this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
      this.paramNameResolver = new ParamNameResolver(configuration, method);
    }
Copy the code

3.3 the execute

// This method is a wrapper call to SqlSession
  public Object execute(SqlSession sqlSession, Object[] args) {
	// Define the return result
    Object result;
	// If it is an INSERT operation
    if (SqlCommandType.INSERT == command.getType()) {
	  // Process parameters
      Object param = method.convertArgsToSqlCommandParam(args);
	  // Call the INSERT method of sqlSession
      result = rowCountResult(sqlSession.insert(command.getName(), param));
	  // Same as above for UPDATE
    } else if (SqlCommandType.UPDATE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.update(command.getName(), param));
	  // Do the same for DELETE
    } else if (SqlCommandType.DELETE == command.getType()) {
      Object param = method.convertArgsToSqlCommandParam(args);
      result = rowCountResult(sqlSession.delete(command.getName(), param));
	  SQL > SELECT (); SQL > SELECT (); SQL > SELECT ()
    } else if (SqlCommandType.SELECT == command.getType()) {
	   // If void is returned and the argument has resultHandler
      Void select(String Statement, Object parameter, ResultHandler handler); methods
      if (method.returnsVoid() && method.hasResultHandler()) {
        executeWithResultHandler(sqlSession, args);
        result = null;
	  
      
        List
       
         selectList(String Statement, Object parameter);
       
      
      // the executeForMany method is called
      } else if (method.returnsMany()) {
        result = executeForMany(sqlSession, args);
	  // Call the executeForMap method if the return type is MAP
      } else if (method.returnsMap()) {
        result = executeForMap(sqlSession, args);
      } else {
		// Query a single objectObject param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); }}else if (SqlCommandType.FLUSH == command.getType()) {
        result = sqlSession.flushStatements();
    } else {
	  // If none match, mapper defines the wrong method
      throw new BindingException("Unknown execution method for: " + command.getName());
    }
	// Throw an exception if the return value is null and the method return value type is base and not VOID
    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;
  }

Copy the code

Depending on the type of SQL statement, it looks like parAM is fetched and then routed to a sqlSession method. Since our selectAll() gets returnsMany=true when executing the MethodSignature method, we follow up

3.4 executeForMany

    If the argument contains rowBounds, the paging query is called
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
    } else {
	// Call a normal query if there is no paging
      result = sqlSession.<E>selectList(command.getName(), param);
    }
Copy the code

The selectList() method of sqlSession continues here:

SqlSession

4.1 SqlSessionTemplate

MapperFactoryBean defines the SqlSession type SqlSessionTemplate in getObject() before creating MapperFactory.


 @Override
  public T getObject(a) throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }
  / / = = >
  public SqlSession getSqlSession(a) {
    return this.sqlSessionTemplate;
  }
  
Copy the code

Let’s look at the selectList() method of the SqlSessionTemplate

@Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.sqlSessionProxy.selectList(statement, parameter);
  }
Copy the code

SqlSessionProxy is once again a proxy object, continuing with the invoke method:

@Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // Get the SqlSqlSession object, if there is no special binding call sqlSessionFactory openSession creation
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        Object result = method.invoke(sqlSession, args);
        if(! isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator ! =null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if(translated ! =null) { unwrapped = translated; }}throw unwrapped;
      } finally {
        if(sqlSession ! =null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); }}}}Copy the code

GetSqlSession if no special configuration is called sessionFactory. OpenSession (executorType) to create:

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      / / create the executor
      final Executor executor = configuration.newExecutor(tx, execType);
      / / return DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
    } finally{ ErrorContext.instance().reset(); }}Copy the code
  • The SqlSession implementation class returned isDefaultSqlSession.
  • ExecutorTy defaults to SIMPLE, which is instantiated hereCachingExecutorEntrusted toSimpleExecutorExecuting specific SQL methods, which have been analyzed in previous articles, is not covered here.

4.2 DefaultSqlSession

Let’s continue with the selectList() method of DefaultSqlSession:

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      / / get MappedStatement
      MappedStatement ms = configuration.getMappedStatement(statement);
      // Call executor's query method
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
    } finally{ ErrorContext.instance().reset(); }}Copy the code

Executor

5.1 CachingExecutor

As mentioned above, CachingExecutor is instantiated when SqlSession is created. Let’s look at the CachingExecutor query method:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      // Get the cache
    Cache cache = ms.getCache();
    if(cache ! =null) {
        // Whether the cache needs to be cleared
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // The implementation class delegated to BaseExecutor continues execution
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // Put it in the cache
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        returnlist; }}// The implementation class delegated to BaseExecutor continues execution
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
Copy the code

CachingExecutor checks to see if there is a cache. If there is no cache, the cache will be written to. Delegat is the delegate object for CachingExecutor, which defaults to SimpleExecutor.

5.2 BaseExecutor

Let’s look at the BaseExecutor parent’s query:

@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // Whether to clear the local cache
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
       // If the query statement already exists in the level-1 cache, it is directly fetched from level-1, whereas it is read from the database
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if(list ! =null) {
        // Here we try to process expressions of type Callable, mainly for parameters of type mode=out
        // Used by stored procedures
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // Fetch it from the database and cache it, which also calls the doQuery() method that subclasses need to overridelist = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      // If the level 1 cache scope is statement level, the level 1 cache is cleared with each query
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482clearLocalCache(); }}return list;
  }
Copy the code

Here is the use of the MyBatis cache, which we will explain in detail later. If the level 1 cache is at the Statement level, it is cleared after each use. Let’s go straight to the queryFromDatabase() method.

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // Place placeholders in the cache
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
    // Delete the placeholder
      localCache.removeObject(key);
    }
    // Add cache
    localCache.putObject(key, list);
    // The stored procedure out parameter is also added to the cache and can be ignored here
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
Copy the code

DoQuery () is the abstract method, and this is actually the doQuery() method that executes SimpleExecutor.

5.3 SimpleExecutor

 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      / / create StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // Create a Statement object. Note that this is the java.sql.Statement object for JDBC
      stmt = prepareStatement(handler, ms.getStatementLog());
      // Execute the SQL statement
      return handler.query(stmt, resultHandler);
    } finally{ closeStatement(stmt); }}Copy the code

There are three main things about doQuery:

  • useconfiguration.newStatementHandlerTo create a StatementHandler, we’re inSection in the previous chapterAs already mentioned, there is no more to explain here.
  • Create a Statement (java.sql.Statement) object.
  • Execute SQL statements using the Statement object.

Creating a Statement object

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
Copy the code

6.1 Obtaining a Java.sql.Connection Object

 protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      returnconnection; }}Copy the code

This transaction is Srping transaction manager SpringManagedTransaction.

  @Override
  public Connection getConnection(a) throws SQLException {
    if (this.connection == null) {
      openConnection();
    }
    return this.connection;
  }
Copy the code

Continuing with openConnection():

private void openConnection(a) throws SQLException {
    this.connection = DataSourceUtils.getConnection(this.dataSource);
    this.autoCommit = this.connection.getAutoCommit();
    this.isConnectionTransactional = DataSourceUtils.isConnectionTransactional(this.connection, this.dataSource);

    LOGGER.debug(() -> "JDBC Connection [" + this.connection + "] will"
        + (this.isConnectionTransactional ? "" : " not ") + "be managed by Spring");
  }
Copy the code

DataSource is our configuration data source in the yml, call org. Springframework). The JDBC dataSource. The DataSourceUtils doGetConnection ().

6.2 Creating a Java.sql.Statement Object

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement. Cause: "+ e, e); }}Copy the code
  • The Statement object is created from the Connection object, with its default implementation classPreparedStatementHandlertheinstantiateStatementMethods.
  • Set the timeout period for the Statement. The timeout period for the Statement is not the timeout period for the entire query, but the timeout period for the Statement to return when the Statement is executed and fetchSize is fetched.
  • Set FetchSize for Statement. This parameter is necessary when a large amount of data is queriedfetchSizeOtherwise full pull is easy OOM.

Continue looking at instantiateStatement() :

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        returnconnection.prepareStatement(sql, keyColumnNames); }}else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      return connection.prepareStatement(sql);
    } else {
      returnconnection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); }}Copy the code

Connection. PrepareStatement is the core of the create Statement object, the connection is a druid connection, by a druid. DruidPooledPreparedStatement rtnVal = new DruidPooledPreparedStatement (this, stmtHolder).

Execute Sql

@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // druid's execute is called
    ps.execute();
    // Execution result
    return resultSetHandler.handleResultSets(ps);
  }
Copy the code

7.1 the execute

Because our data source connection pool is configured with Druid. Execute calls druid’s implementation:

 @Override
    public boolean execute(a) throws SQLException {
        checkOpen();
        // Increase the number of SQL executions
        incrementExecuteCount();
        // Save the SQL in transactionInfo
        transactionRecord(sql);

        // oracleSetRowPrefetch();

        conn.beforeExecute();
        try {
        // Execute JDBC
            return stmt.execute();
        } catch (Throwable t) {
            throw checkException(t);
        } finally{ conn.afterExecute(); }}Copy the code

7.2 handleResultSets

7.2.1 Entry of Mapping Results

public List<Object> handleResultSets(Statement stmt) throws SQLException {
   ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

   final List<Object> multipleResults = new ArrayList<Object>();

   int resultSetCount = 0;
   // The first result of the result set
   ResultSetWrapper rsw = getFirstResultSet(stmt);

   List<ResultMap> resultMaps = mappedStatement.getResultMaps();
   int resultMapCount = resultMaps.size();
   validateResultMapsCount(rsw, resultMapCount);
   while(rsw ! =null && resultMapCount > resultSetCount) {
     ResultMap resultMap = resultMaps.get(resultSetCount);
     // Generate Java objects according to resultMap processing RSW
     handleResultSet(rsw, resultMap, multipleResults, null);
     // Get the next result of the result set
     rsw = getNextResultSet(stmt);
     cleanUpAfterHandlingResultSet();
     resultSetCount++;
   }

   String[] resultSets = mappedStatement.getResultSets();
   if(resultSets ! =null) {
   	// Similar to the traversal handling of resultMaps
     while(rsw ! =null && resultSetCount < resultSets.length) {
       ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
       if(parentMapping ! =null) {
         String nestedResultMapId = parentMapping.getNestedResultMapId();
         ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
         // Process the result
         handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; }}return collapseSingleResultList(multipleResults);
 }
Copy the code

ResultMap is the core of handleResultSets method, when mybatis initialization after the completion of the above configuration in MappedStatement. ResultMaps inside, During parsing, the corresponding resultMap is obtained through resultmap. id and then parsed one by one. HandleResultSet (RSW, resultMap, NULL, parentMapping)

  private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
    try {
      if(parentMapping ! =null) {
        handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
      } else {
        if (resultHandler == null) {
           // Create a default result handler
            DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
            // Process the row data of the result set
            handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
            // Add results to multipleResults
            multipleResults.add(defaultResultHandler.getResultList());
        } else {
          handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); }}}finally {
      // issue #228 (close resultsets)closeResultSet(rsw.getResultSet()); }}Copy the code

It can be seen that although there are 3 cases according to parentMapping and resultHandler, they all enter handleRowValues method in the end. Let’s look at the handleRowValues method:

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler
        resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
    // A nested structure
    if (resultMap.hasNestedResultMaps()) {
      ensureNoRowBounds();
      checkResultHandler();
      handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
    } else {
     // To deal with simple mappings, this article will analyze only simple mappings firsthandleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); }}Copy the code

This article examines simple mappings only, and I’ll write a separate analysis of the handling of nested structures later. Continue to see handleRowValuesForSimpleResultMap:

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler
        resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
    ResultSet resultSet = rsw.getResultSet();
        // Locate the specified row record according to RowBounds
    skipRows(resultSet, rowBounds);
    while(shouldProcessMoreRows(resultContext, rowBounds) && ! resultSet.isClosed() && resultSet.next()) {// Use discriminate to find suitable ResultMap
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
      // Generate Java objects with lazy loading
      Object rowValue = getRowValue(rsw, discriminatedResultMap, null); storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); }}Copy the code

7.2.2 Creating Entity Objects

Let’s trace createResultObject in getRowValue() to see how Java objects are created:

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
    this.useConstructorMappings = false; // reset previous mapping result
    finalList<Class<? >> constructorArgTypes =new ArrayList<>();
    final List<Object> constructorArgs = new ArrayList<>();
    // Create an object
    Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
    if(resultObject ! =null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
      final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
      for (ResultMapping propertyMapping : propertyMappings) {
        // issue gcode #109 && issue #149
       /* If lazy loading is enabled, proxy classes are generated for resultObject. If only configured associative queries are enabled, proxy classes are not created without lazy loading. * Proxy classes are created using the Javassist framework by default. * Because entity classes typically do not implement interfaces, you cannot use the JDK dynamic proxy API to generate proxies for entity classes. * and pass in the lazyLoader */
        if(propertyMapping.getNestedQueryId() ! =null && propertyMapping.isLazy()) {
          resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
          break; }}}this.useConstructorMappings = resultObject ! =null && !constructorArgTypes.isEmpty(); // set current mapping result
    return resultObject;
  }
Copy the code

Moving on to Create objects:

private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List
       
        > constructorArgTypes, List
         constructorArgs, String columnPrefix)
       >
    throws SQLException {
  finalClass<? > resultType = resultMap.getType();final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
  final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
  if (hasTypeHandlerForResultObject(rsw, resultType)) {
  // There are typeHanlder classes that are applicable, which in fact are generally primitive data types or their encapsulated classes
    return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
  } else if(! constructorMappings.isEmpty()) {// Constructor mapping with arguments
    return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix);
  } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
  // interface or no-argument constructor
    return objectFactory.create(resultType);
  } else if (shouldApplyAutomaticMappings(resultMap, false)) {
  // Automatic mapping with argument constructors
    return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix);
  }
  throw new ExecutorException("Do not know how to create an instance of " + resultType);
}
Copy the code

7.2.3 Result Set Mapping

Here we return to getRowValue (), whether to automatically map respectively do the processing, we first see processing automatic mapping applyAutomaticMappings, his judgment on the condition that shouldApplyAutomaticMappings:

private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
//resultMap configures autoMapping = true
  if(resultMap.getAutoMapping() ! =null) {
    return resultMap.getAutoMapping();
  } else {
  // XML setting autoMappingBehavior: NONE, PARTIAL, FULL
    if (isNested) {
      return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
    } else {
      returnAutoMappingBehavior.NONE ! = configuration.getAutoMappingBehavior(); }}}Copy the code
  • applyAutomaticMappings: There are columns in the result set, but the resultMap is not configured.
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
// Create a mapping pair for automatic mapping
  List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
  boolean foundValues = false;
  if(! autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {
     // Get the specified column data from the result set via TypeHandler
      final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
      if(value ! =null) {
        foundValues = true;
      }
      if(value ! =null|| (configuration.isCallSettersOnNulls() && ! mapping.primitive)) {// gcode issue #377, call setter on nulls (value is not 'found')
      // Set value to the specified field of the entity-class object via the meta-information objectmetaObject.setValue(mapping.property, value); }}}return foundValues;
}
Copy the code
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
  final String mapKey = resultMap.getId() + ":" + columnPrefix;
  //autoMappingsCache is used as a cache and is first fetched from the cache
  List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
  // Cache failed to hit
  if (autoMapping == null) {
    autoMapping = new ArrayList<UnMappedColumnAutoMapping>();
    final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
    for (String columnName : unmappedColumnNames) {
      String propertyName = columnName;
      if(columnPrefix ! =null && !columnPrefix.isEmpty()) {
        // When columnPrefix is specified,
        // ignore columns without the prefix.
        if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
          propertyName = columnName.substring(columnPrefix.length());
        } else {
          continue; }}/ / hump
      final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
      // The set method exists
      if(property ! =null && metaObject.hasSetter(property)) {
      // The resultMap mapping already exists and is ignored
        if (resultMap.getMappedProperties().contains(property)) {
          continue;
        }
        finalClass<? > propertyType = metaObject.getSetterType(property);if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
          finalTypeHandler<? > typeHandler = rsw.getTypeHandler(propertyType, columnName); autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
        } else {
        / / not found, according to the autoMappingUnknownColumnBehavior attributes (defaults to NONE) processing: NONE (ignore), WARNING (log. Warn), ERROR (cast)configuration.getAutoMappingUnknownColumnBehavior() .doAction(mappedStatement, columnName, property, propertyType); }}else {
      / / / / not found, according to the autoMappingUnknownColumnBehavior properties (the default is NONE) processing: NONE (ignore), WARNING (log. Warn), ERROR (cast)configuration.getAutoMappingUnknownColumnBehavior() .doAction(mappedStatement, columnName, (property ! =null)? property : propertyName,null); }}// add cache
    autoMappingsCache.put(mapKey, autoMapping);
  }
  return autoMapping;
}
Copy the code
  • applyPropertyMappings: according to the<resultMap>The mapping is configured on the node
 private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
    throws SQLException {
  final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
  boolean foundValues = false;
  final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
  for (ResultMapping propertyMapping : propertyMappings) {
    String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
    if(propertyMapping.getNestedResultMapId() ! =null) {
      // the user added a column attribute to a nested result map, ignore it
      // Nested query properties, ignoring column
      column = null;
    }
    if(propertyMapping.isCompositeResult() || (column ! =null&& mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) || propertyMapping.getResultSet() ! =null) {
        / / field values
      Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
      // issue #541 make property optional
      final String property = propertyMapping.getProperty();
      if (property == null) {
        continue;
      } else if (value == DEFERED) {
        foundValues = true;
        continue;
      }
      if(value ! =null) {
        foundValues = true;
      }
      // Assign to the object
      if(value ! =null|| (configuration.isCallSettersOnNulls() && ! metaObject.getSetterType(property).isPrimitive())) {// gcode issue #377, call setter on nulls (value is not 'found')metaObject.setValue(property, value); }}}return foundValues;
}
Copy the code

Obtain the ResultMapping collection of mapping objects from ResultMap. Then the ResultMapping collection is traversed, and getPropertyMappingValue is called during this process to obtain the specified column data, and finally the obtained data is set to the entity class object.

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {

    if(propertyMapping.getNestedQueryId() ! =null) {
        // Obtain the associated query result
        return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if(propertyMapping.getResultSet() ! =null) {
        addPendingChildRelation(rs, metaResultObject, propertyMapping);
        return DEFERED;
    } else {
        finalTypeHandler<? > typeHandler = propertyMapping.getTypeHandler();final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
        Retrieves the value of the specified column from the ResultSet
        returntypeHandler.getResult(rs, column); }}Copy the code

HandleResultSets can be used to query handleResultSets.