Public search “code road mark”, point of concern not lost!

The last article “Mybatis source CODE SqlSession” talked about THE SqlSession is actually a contractor head, pull their own work is not arranged to the executer; And in “Mybatis source SQL execution process” has learned that the executer through the StatementHandler life cycle scheduling and management, the final completion of SQL command execution.

Today we will take a look at one of the four components of Mybatis: Executor. To be clear, since Executor is involved in Mybatis level 1 cache and Level 2 cache, this section will be discussed separately in this article.

Executor profile

Executor resides in the Executor package, which schedules the execution of all SQL commands in Mybatis. First, take a look at the Executor family through a UML class diagram:

  • At the top level is the Executor interface, which defines interface methods related to query, update, transaction, and cache operations. The Executor interface is exposed, dependent on, scheduled, and managed by SqlSession.
  • The second layer on the left is BaseExecutor, which is an abstract class that implements most Executor interfaces. It has three subclasses, namely SimpleExecutor, ReuseExecutor, and BatchExecutor. BaseExecutor and its subclasses perform level 1 cache management and operations related to database interaction.
  • The second layer on the right is CachingExecutor, the cache executor, the core processing class of Mybatis level 2 cache. CachingExecutor holds an instance of a BaseExecutor implementation class (SimpleExecutor, ReuseExecutor, or BatchExecutor) as the delegate executor. It mainly completes the Mybatis level 2 cache processing logic, when the cache query does not exist or cannot query the result, it will query the database through the delegate executor.
  • The third layer is the three subclasses of BaseExecutor. The simple actuator is the default actuator and has all the capabilities of the actuator. A reusable executor is a simple executor. It has the caching and reuse capabilities of MappedStatement. That is, if the same command is executed repeatedly in a SqlSession, the cached MappedStatement can be directly reused. Batch executor, that is, multiple commands can be executed at a time.

Executor’s core function is to schedule the execution of SQL. To improve query performance, Mybatis designs level 1 cache and level 2 cache in Executor. Level 1 cache is implemented by BaseExecutor and its subclasses, and level 2 cache is implemented by CachingExecutor. Level-1 cache is enabled by default. Level-2 cache must be enabled. Because CachingExecutor is responsible for cache management, and the real database queries are done by BaseExecutor, there are three Executor types externally: SIMPLE, REUSE, and BATCH. The default is SIMPLE. We can change the default executor type by specifying parameters in the configuration file or when creating the SqlSession. The following is the definition of ExecutorType in Mybatis:

public enum ExecutorType {
    // Simple actuator
    SIMPLE, 
    // A reusable actuator
    REUSE, 
    // Batch executor
    BATCH
}
Copy the code

Creation Process Analysis

As mentioned earlier, SqlSession relies on Executor, which accepts SqlSession requests and executes them, and Executor creation is created with SqlSession creation. The Executor creation process starts with DefaultSqlSessionFactory#openSession() and its overloaded methods:

  @Override
  public SqlSession openSession(a) {
    // Use the default actuator type: SIMPLE
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
  }

  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);
      / / by configuration. NewExecutor method to create executor
      final Executor executor = configuration.newExecutor(tx, execType);
      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

Simple analysis about this code: here by countless openSession method, its internal use Configuration# defaultExecutorType as actuator types call openSessionFromDataSource method; The latter calls Configuration#newExecutor(), which does nothing for execType, to execute the Executor creation process. Configuration#defaultExecutorType defaults to SIMPLE, which will be modified when Mybatis parses the configuration file. If the configuration file does not involve executorType configuration, the default value does not change. Alternatively, an overloaded method of openSession can be called to specify the executor type. The two Settings are as follows:

  • The default actuator type of Mybatis is SIMPLE, which can be modified in the configuration file:
    <settings>
        <setting name="defaultExecutorType" value="SIMPLE"/>
    </settings>
Copy the code
  • Or set it in code:
REUSE specifies the REUSE actuator type
try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)) {
    / /...
}
Copy the code

Knowing how to set up ExecutorType, look at the Configuration#newExecutor() method:

//Executor creates an Executor method
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    //executorType is null: use the default executor; otherwise, use the input type
    ExecutorType defaultExecutorType = executortype. SIMPLE;
    executorType = executorType == null ? defaultExecutorType : executorType;
    //executorType and defaultExecutorType are both null, and SIMPLE is used by default
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // Create different executorTypes according to executorType.
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    // The default value is true.
    // If the configuration file is not modified, the if statement will be executed
    if (cacheEnabled) {
      // Create a cache executor with the executor created above as its delegate executor
      executor = new CachingExecutor(executor);
    }
    // Load Executor related plugins.
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
Copy the code

To summarize the implementation process:

  • First: process the actuator type parameter to ensure that it is not empty, and finally to SIMPLE bottom;
  • Second, call the corresponding executor implementation class to initialize the executor according to the executor type. The executor can be SimpleExecutor, ReuseExecutor, or BatchExecutor.
  • Then: If level 2 caching is enabled (cacheEnabled defaults to True, but it does not work if you do not set cache parameters), create a cache executor and use the executor created above as a delegate executor for the cache executor.
  • Finally: Load executor-related plug-ins.


By default, a CachingExecutor object is returned with some subclass of BaseExecutor wrapped inside.

The query process

The Executor interface has two overloads of the Query method. We start with the one with fewer arguments, and it calls the other one internally. CachingExecutor is used by default, so we’ll start with CachingExecutor#query() :

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    / / get BoundSql
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // Create a cache key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // Call the overloaded query method
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // Get the cache object
    Cache cache = ms.getCache();
    // The cache is not empty
    if(cache ! =null) {
      // Flush the cache
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        // Query from the cache
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          // The cache does not have any
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        returnlist; }}// Level 2 cache is not enabled by default
    Delegate is one of the three subclasses of BaseExecutor
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
Copy the code

Enter CachingExecutor# Query, first fetch BoundSql through MappedStatement, create the cache key, and then call the overloaded Query method. Overloaded Query queries are queried directly through the delegate executor’s Query method without regard to caching. The delegate executor is a subclass of BaseExecutor, which implements the Query method, so we’ll start with BaseExecutor#query() (again ignoring the logic of the level-i cache) :

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.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // Query from level 1 cache (local cache)
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if(list ! =null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // Do not exist in cache, query databaselist = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482clearLocalCache(); }}return list;
  }

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // Cache placeholder
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // Call abstract methods to perform database queries, subclass implementation
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // Remove the placeholder
      localCache.removeObject(key);
    }
    // Set the cache
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
Copy the code

The two methods are longer and are mostly cached. Ignoring the cache (see my comments directly), the database query method queryFromDatabase is called from the Query method, but the real query logic is implemented in the abstract method doQuery, which is implemented by a BaseExecutor subclass. Let’s look at the subclass implementation logic in turn:

SimpleExecutor#doQuery

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // Get the configuration object
      Configuration configuration = ms.getConfiguration();
      / / create StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      / / for the Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      // Execute the query
      return handler.query(stmt, resultHandler);
    } finally {
      / / close the StatementcloseStatement(stmt); }}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

The code for the SimpleExecutor#doQuery method is relatively simple, and the process is straightforward. Here’s a brief description:

  • Get the global Configuration object from the MappedStatement object.
  • Call Configuration#newStatementHandler to create the StatementHandler object.
  • Create and initialize the Statement object.
  • Call StatementHandler#query to execute the Statement and use resultHandler to parse the return value.
  • Finally, close the Statement.

DoQuery scheduling StatementHandler completes the initialization, parameter setting, execution, result processing, and closing of a Statement, and manages and controls the entire life cycle of the Statement. As mentioned above, executors are involved in the entire execution of SQL statements.

ReuseExecutor#doQuery

  / / the Statement cache
  private final Map<String, Statement> statementMap = new HashMap<>();

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  }

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    // Check whether the current SQL exists in the cache
    if (hasStatementFor(sql)) {
      // If you have one, use it directly
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      // If not, create a new one
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      // Then cache it.
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }
Copy the code

The logic of ReuseExecutor#doQuery is basically the same as that of SimpleExecutor#doQuery, except for the implementation logic of the prepareStatement method. PrepareStatement uses statementMap to cache executed SQL. The creation process is executed only when statementMap does not contain the current SQL, which improves performance. Note that Executor objects are part of the SqlSession, so this cache is consistent with the LIFECYCLE of the SqlSession.

BatchExecutor#doQuery

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException {
    Statement stmt = null;
    try {
      // Execute the statement in batches
      flushStatements();
      // Obtain the Configuration object
      Configuration configuration = ms.getConfiguration();
      / / create StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
      / / create the Statement
      Connection connection = getConnection(ms.getStatementLog());
      stmt = handler.prepare(connection, transaction.getTimeout());
      // Set the Statement parameter
      handler.parameterize(stmt);
      // Execute and return the result
      return handler.query(stmt, resultHandler);
    } finally{ closeStatement(stmt); }}Copy the code

The BatchExecutor#doQuery method performs more flushStatements than SimpleExecutor and no longer expands.

The update () process

The update() method corresponds to insert, update, delete and other commands, and its execution flow is similar to that of the query() method. Is also a CachingExecutor – > BaseExecutor – > SimpleExecutor/ReuseExecutor/BatchExecutor. Post the entire process code together for analysis (comment) :

  //org.apache.ibatis.executor.CachingExecutor#update
  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    // Refresh cache: When caching is enabled, the update command refreshes the cache by default
    flushCacheIfRequired(ms);
    // Call the delegate executor to update
    return delegate.update(ms, parameterObject);
  }

  //org.apache.ibatis.executor.BaseExecutor#update
  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // Clear the cache and call the subclass's doUpdate method
    clearLocalCache();
    // Call the abstract method to perform the database update operation
    return doUpdate(ms, parameter);
  }
Copy the code

Mybatis cache only works for queries, so all Executor can do is invalidate the cache, relay the request, and finally call doUpdate to perform the database operation, so:

  • CachingExecutor#update first invalidates the secondary cache, and then invokes the delegate executor to perform the update operation.
  • BaseExecutor#update also invalidates the level-1 cache first, and then calls the abstract method doUpdate to perform database updates.

SimpleExecutor#doUpdate

SimpleExecutor#doUpdate is exactly the same as doQuery, no further explanation.

ReuseExecutor#doUpdate

ReuseExecutor#doUpdate is exactly the same as doQuery, no further clarification.

BatchExecutor#doUpdate

The BatchExecutor execution is different from the first two in that it is used to execute batch SQL commands, so there is a bit more batch preparation. To reduce the number of interactions with the database, BatchExecutor executes SQL commands in batches. The code is as follows:

  @Override
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
    // Obtain the Configuration object
    final Configuration configuration = ms.getConfiguration();
    / / create StatementHandler
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null.null);
    // Get the SQL object
    final BoundSql boundSql = handler.getBoundSql();
    // Get SQL and wine
    final String sql = boundSql.getSql();
    final Statement stmt;
    // If the current command is the same as the last one, the Statement is not created again and the performance is improved
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
      // Retrieve the index of the last item
      int last = statementList.size() - 1;
      // Retrieve the last Statement object
      stmt = statementList.get(last);
      applyTransactionTimeout(stmt);
      // Set the Statement parameter
      handler.parameterize(stmt);//fix Issues 322
      / / get BatchResult
      BatchResult batchResult = batchResultList.get(last);
      // Set the BatchResult parameter object
      batchResult.addParameterObject(parameterObject);
    } else {
      // If the current command is different from the last one, create it again
      Connection connection = getConnection(ms.getStatementLog());
      // Initialize Statement
      stmt = handler.prepare(connection, transaction.getTimeout());
      // Set parameters
      handler.parameterize(stmt);    //fix Issues 322
      // Set the current SQL command information
      currentSql = sql;
      currentStatement = ms;
      / / save it
      statementList.add(stmt);
      // Save the result object
      batchResultList.add(new BatchResult(ms, sql, parameterObject));
    }
    // Batch processing
    handler.batch(stmt);
    return BATCH_UPDATE_RETURN_VALUE;
  }
Copy the code

The BatchExecutor#doUpdate method prepares the Statement before executing it. When preparing the Statement, compare it with the Statement to be executed last time. If the Statement is the same, the process of recreating the Statement is not performed. Therefore, you should try to execute the same SQL commands when using BatchExecutor.

However, BatchExecutor#doUpdate does not perform database execution, it needs to be triggered by sqlssession #flushStatements, and then called to BatchExecutor#doFlushStatements to perform the final operation, I’m not going to expand it here.

Executor summary

Executor’s update/query execution process is described in detail in this article. The Executor’s update/query execution process is described in detail in this article. The Executor’s update/query execution process is described in detail in this article. Executor caching is something we haven’t expanded. Executor is one of the four components of Mybatis. Although we have not studied the other three components, we have already had an overall understanding of Mybatis SQL execution process, which shows that Executor plays a very important role.

That’s the end of this article, I hope it was useful to you! My level is limited, if you find any mistakes or improper place, welcome to criticize and correct.

Public search “code road mark”, point of concern not lost!