MyBaits series - Executor of MyBatis source code

Knowledge points

  • What Executor executors does Myabtis have and what are the differences?
  • How does Mybatis configure which Executor to use?
  • How does Mybatis implement batch processing?
  • Mybatis's primary key policy, batch write, can you return the database primary key?

The main structure and function of Executor package

Executor interface and its implementation class, usage scenario and its call

BaseExecutor

BaseExecutor creates a new local cache, and gets it from the cache every time it executes query. If it doesn't, it gets it from the database and puts it into the cache. The code is as follows:

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 it has been closed, an error is reported
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    //Clear the local cache first, and then query. But only query stack is 0. To handle recursive calls
    if (queryStack == 0 &amp;&amp; ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<e> list;
    try {
      //Add one, so that the local cache will not be cleared when the recursive call reaches the top
      queryStack++;
      //First, check from localCache according to cachekey
      list = resultHandler == null ? (List<e>) localCache.getObject(key) : null;
      if (list != null) {
        //If the localCache cache is found, process the localOutputParameterCache
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //Check from database
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      //Emptying stack
      queryStack--;
    }
    if (queryStack == 0) {
      //Delay loading all elements in the queue
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      //Clear delay load queue
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
    	//If it is a STATEMENT, clear the local cache
        clearLocalCache();
      }
    }
    return list;
  }

You can see that if LocalCacheScope is STATEMENT, the cache is emptied after each call. So, how is the value of LocalCacheScope configured?
settings node Configuration of Configuration: SESSION,STATEMENT

Get data from database:

  public <e> List<e> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<e> handleResultSets(ps);
  }
SimpleExecutor:

Get a new statement every time

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    //Call StatementHandler.prepare
    stmt = handler.prepare(connection);
    //Call StatementHandler.parameterize
    handler.parameterize(stmt);
    return stmt;
  }
ReuseExecutor:

Get statement from hashmap

  //Reuseexecutior declares a reusable map to cache the Statement corresponding to the SQL Statement
 private final Map<string, statement> statementMap = new HashMap<string, statement>();
  ...
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    //Get bound SQL statement
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    //If it already exists in the cache, the Statement will be obtained directly
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
    } else {
      //If the cache is not found, the process is exactly the same as that of simpleexector, and then the cache is added
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection);
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }
 ...
 private Statement getStatement(String s) {
    return statementMap.get(s);
  }
BatchExecutor: can be updated in batch.

Save multiple executed statement s for one execution. The core code is as follows:

  @Override
  public void batch(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    //Call Statement.addBatch
    statement.addBatch(sql);
  }
 

How to configure the default exciter?
Configuration settings node configuration: defaultExecutorType, value is SIMPLE,REUSE,BATCH

CachingExecutor
keygen

keygen provides the following strategies for primary key generation:

  • Jdbc3keygenerator: JDBC 3 key value generator. The core is to use the Statement.getGeneratedKeys of JDBC 3 to generate the primary key after execution. The core code is as follows:
//Get primary key collection
ResultSet resultSet = statement.getGeneratedKeys();
//Backfill to object
...
  • SelectKeyGenerator: generates the primary key according to sql. It supports generating the primary key before execution and generating the primary key after execution.
//Get primary key collection
Executor keyExecutor = configuration.newExecutor(executor.getTransaction(), ExecutorType.SIMPLE);
      List<object> values = keyExecutor.query(keyStatement, parameter, RowBounds.DEFAULT, Executor.NO_RESULT_HANDLER);
//Backfill to object
  • NoKeyGenerator: do not generate primary key

  • Be careful:

    • XML configuration: if the configured useGeneratedKeys is true and the insert statement is executed, the Jdbc3KeyGenerator is used by default, otherwise it is NoKeyGenerator
    • Annotation method: if it is an insert or update statement, first judge the @ SelectKey annotation, then the @ Options annotation. If not, judge the useGeneratedKeys in the default configuration. Otherwise, execute according to the @ Option annotation. Therefore, if the global useGeneratedKeys is configured to be true, you need to set + + @ Option(useGeneratedKeys=false) when updating++
if (SqlCommandType.INSERT.equals(sqlCommandType) ||              SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // first check for SelectKey annotation - that overrides everything else
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
        } else {
          keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
} else {
    keyGenerator = new NoKeyGenerator();
}
//Test1
    @Insert("insert into table2 (name) values(#{name})")
    @SelectKey(statement="select id, name_fred from table2 where id = identity()", keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED", before=false, resultType=Map.class)
    int insertTable2WithSelectKeyWithKeyMap(Name name);
//Test2
    @Insert("insert into table2 (name) values(#{name})")
    @Options(useGeneratedKeys=true, keyProperty="nameId,generatedName", keyColumn="ID,NAME_FRED")
    int insertTable2WithGeneratedKey(Name name);
```</object></string,></string,></e></e></e></e></e></e></e>

Tags: Programming SQL Database Mybatis JDBC

Posted on Tue, 14 Jan 2020 00:30:26 -0800 by billynastie2007