Mybatis source learning (27) - Executor in mybatis

I. Introduction

          is one of the most core interfaces of Mybatis. Among them, the functions in the SqlSession interface are based on the exciter.

//DefaultSqlSession.java
@Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

   the above method is a select method in the default implementation class DefaultSqlSession of Mybatis of SqlSession interface. It can be seen from the code that the query is finally implemented through the query method of exciter.

2, Executor interface and its hierarchy

                    in Mybatis, the implementation of the Executor interface consists of two types: one is the real implementation class of the interface, which is used to operate the database; the other. The hierarchy of Executor interface is shown in the figure below:

   among them, BaseExecutor is the abstract implementation class of the exciter interface, which mainly provides the basic functions of cache management and transaction management; simpleexecutior inherits the BaseExecutor abstract class, which is the simplest implementation of the Executor interface provided by Mybatis; reuseexecutior also inherits the BaseExecutor abstract class, which provides the function of reusing Statement; BatchExecutor also inherits baseexec UTOR abstract class implements the function of batch processing multiple SQL statements; closexecution also inherits BaseExecutor abstract class, and is an internal class of ResultLoaderMap class, which is used to implement lazy load related logic. The cachenexecutor class directly implements the Excutor interface, which is a decorator class. It mainly enhances cache related functions.

Executor interface:

public interface Executor {

  /**
   * The default result processor, which is empty by default, is mainly used to process query results during query
   */
  ResultHandler NO_RESULT_HANDLER = null;
  /**
   * Insert / update / delete the data table
   * @param ms
   * @param parameter
   * @return
   * @throws SQLException
   */
  int update(MappedStatement ms, Object parameter) throws SQLException;
  /**
   * Query, callback processing query results, with paging
   * @param ms
   * @param parameter
   * @param rowBounds
   * @param resultHandler
   * @param cacheKey
   * @param boundSql
   * @return
   * @throws SQLException
   */
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
  /**
   * Query, callback processing query results,
   * @param ms
   * @param parameter
   * @param rowBounds
   * @param resultHandler
   * @param cacheKey
   * @param boundSql
   * @return
   * @throws SQLException
   */
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
  /**
   * Query, return cursor < E > type
   * @param ms
   * @param parameter
   * @param rowBounds
   * @return
   * @throws SQLException
   */
  <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
  /**
   * Batch SQL statements
   * @return
   * @throws SQLException
   */
  List<BatchResult> flushStatements() throws SQLException;
  /**
   * Submission of affairs
   * @param required
   * @throws SQLException
   */
  void commit(boolean required) throws SQLException;
  /**
   * Rollback transaction
   * @param required
   * @throws SQLException
   */
  void rollback(boolean required) throws SQLException;
  /**
   * Create the CacheKey used in the cache, that is, the key used in the cache
   * @param ms
   * @param parameterObject
   * @param rowBounds
   * @param boundSql
   * @return
   */
  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
  /**
   * Find whether there is cache according to the corresponding key
   * @param ms
   * @param key
   * @return
   */
  boolean isCached(MappedStatement ms, CacheKey key);
  /**
   * Clean up the first level cache
   */
  void clearLocalCache();
  /**
   * Delay loading data in L1 cache
   * @param ms
   * @param resultObject
   * @param property
   * @param key
   * @param targetType
   */
  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
  /**
   * Get transaction object
   * @return
   */
  Transaction getTransaction();
  /**
   * Close the Executor object
   * @param forceRollback
   */
  void close(boolean forceRollback);
  /**
   * Check whether the exciter object is closed
   * @return
   */
  boolean isClosed();
  /**
   * Set packaging class
   * @param executor
   */
  void setExecutorWrapper(Executor executor);

}
3, BaseExecutor abstract class

                      .

1, attribute
/**
   * Transaction Object to implement transaction commit, rollback and close operations
   */
  protected Transaction transaction;
  /**
   * Encapsulates the real Executor object
   */
  protected Executor wrapper;
  /**
   * Delay loading the queue. An unbounded thread safe queue based on linked nodes. This queue sorts elements according to the FIFO (first in, first out) principle.
   * When multiple threads share access to a common collection, ConcurrentLinkedQueue is an appropriate choice. null elements are not allowed for this queue.
   * This variable mainly stores some variable objects that can delay loading, and can be used by multiple threads
   */
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  /**
   * The first level cache is used to cache the result object obtained from the query result set mapping of the Executor object,
   */
  protected PerpetualCache localCache;
  /**
   * First level cache, used to cache parameters of output type
   */
  protected PerpetualCache localOutputParameterCache;
  /**
   * Globally unique configuration object
   */
  protected Configuration configuration;
  /**
   * It is used to record the number of layers of nested query and analyze the nested query introduced in DefaultResultSetHandler
   */
  protected int queryStack;
  /**
   * Identify whether the actuator has been closed
   */
  private boolean closed;
 
2. Constructor

   mainly implements the initialization of the above property fields. Among them, locaCache and localOutputParameterCache all use the type of perpetual cache.

 /**
   * Constructors, initializing variables
   * @param configuration
   * @param transaction
   */
  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }
3. Cache related operations
  1. createCacheKey() method
    Create the key used in the cache, that is, the CacheKey instance object. The uniqueness of the created CacheKey instance object is determined by the following parameters, that is, the same query, related parameters, and the generated CacheKey instance are unique and unchanged. The parameters are as follows:
    1. ID of MappedStatement instance
    2. offset and limit properties of RowBounds instance
    3. sql attribute of BoundSql instance
    4. Parameter value of query condition
    5. Environment ID of the connection database
@Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }
  1. isCached()
    Determine whether the query result is cached.
@Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return localCache.getObject(key) != null;
  }
  1. clearLocalCache()
    The method to clear the local cache is as follows:
 @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
4. Transaction related methods

Among them, clearLocalCache(), flushStatements() methods are executed in commit() and rollback() methods, and the cache and Statement are refreshed.

  /**
   * Get the transaction object in the executor
   */
  @Override
  public Transaction getTransaction() {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return transaction;
  }
  /**
   * Transaction commit. The real commit operation is completed by transaction instance transaction
   * This method realizes:
   * 1,Determine whether the actuator is closed. If it is closed, throw an exception directly
   * 2,Clean up cache and perform pre operations
   * 3,Judge whether to perform submission operation according to parameters
   */
  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    clearLocalCache();
    flushStatements();
    if (required) {//If
      transaction.commit();
    }
  }
  /**
   * Rollback operation
   */
  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {//If the actuator is not closed, the rollback operation will be performed. Otherwise, it will be ignored and no operation will be performed
      try {
    	//Clear cached content
        clearLocalCache();
        //Refresh, preprocess
        flushStatements(true);
      } finally {
        if (required) {//If rollback is required, perform the rollback operation of the transaction
          transaction.rollback();
        }
      }
    }
  }
5. Close method
/**
   * Close the Statement instance of java
   * @param statement
   */
  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        if (!statement.isClosed()) {
          statement.close();
        }
      } catch (SQLException e) {
        // ignore
      }
    }
  }
  /**
   * Close the current actuator and perform the rollback operation if rollback is required
   * The corresponding resources (such as transaction, etc.) need to be closed synchronously
   * Set other variables to NULL
   */
  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore.  There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }
  /**
   * Gets the ID of whether the current actuator is closed
   */
  @Override
  public boolean isClosed() {
    return closed;
  }
6. Delay loading method

                      .

7. Update operation update()

Update operation, this method handles the general situation, and the real implementation is done by the subclass through implementing the doUpdate method. This method realizes:
1. Judge whether the actuator has been closed. If it is closed, throw an exception directly
2. Because this operation is an update operation, it will invalidate all the caches in it, that is, clean up the cache
3. Call the abstract method doUpdate to update the data, which is completed by the subclass.

@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.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }
protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;
8. Refresh Statement

Refresh Statement. The real implementation is completed by specific subclasses, and the logic in different implementation classes is different. We will analyze it later. The general method mainly realizes:
1. Determine whether the actuator is closed, and throw an exception directly if it is closed
2. Call the abstract method doFlushStatements, and the real implementation is completed by the subclass

 @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }
  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return doFlushStatements(isRollBack);
  }
  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
      throws SQLException;
9. query() method
@Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

In the above method, the main way is to get the BoundSql instance object based on MappedStatement, and to generate the CacheKey instance object, then call the overload query() method to implement the query.

 @SuppressWarnings("unchecked")
  @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.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      //Query data in L1 cache if result processor = = null
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
    	//The function of the stored procedure call processing is to obtain the output type parameters saved in the cache when the first level cache hits,
    	//And set the bean to the parameter object passed in by the user.
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {//Otherwise, execute the following statement to query the data from the database
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      //Determine whether to clear the local cache according to the cache scope
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

   in this method, first judge whether the current actuator has been closed; then judge whether to clear the first level cache according to the queryStack query layer number and flushCache attribute; then judge whether the result processor resultHandler is empty. If it is empty, try to query the data from the cache, otherwise it will be empty directly, and then query the data from the database; then according to the cache If there is data in the storage, if there is data and it is a stored procedure or function type, execute handlelocallycachedoutputetparameters(). If there is data, it will be returned directly if it is not a stored procedure or function type. If there is no data in the cache, it will be queried from the database directly through the queryFromDatabase() method, and the query logic will be realized through the doQuery() method Later, the deferred loading function of nested query is realized through the DeferredLoad class.

Handlelocallycachedoutparameters() method:

/**
   * The function of the stored procedure call is to get the output type parameters saved in the cache and set them to the parameter object passed in by the user when the first level cache hits.
   * @param ms
   * @param key
   * @param parameter
   * @param boundSql
   */
  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {//CALLABLE type, i.e. stored procedure and function type, otherwise no processing will be done
      //Get cached objects according to key
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {//If neither the cache object nor the parameter is null
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
          if (parameterMapping.getMode() != ParameterMode.IN) {
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

queryFromDatabase() method:
Among them, the real query operation logic is realized by calling the abstract method doQuery() which needs to be implemented by subclass.

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
10. queryCursor() method

Query, return Cursor type. This method mainly completes the work of generating the BoundSql instance according to the parameters. The real query work is done by the implementation of doQueryCursor method by the subclass.

@Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;
4, Simpleexecutior class

                      . In the simpleexector class, four abstract methods in the BaseExecutor abstract class are mainly implemented.

1. doUpdate() method

                               Q Q Q  The update() method of ndler implements the update interaction with data.

@Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

The prepareStatement() method is used to build the java.sql.Statement instance object and bind the corresponding parameters.

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;
  }
2. doQuery(), doQueryCursor() methods

These two methods are similar to the logic of the doUpdate() method.

@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();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.<E>queryCursor(stmt);
  }
3. doFlushStatements() method

   the doFlushStatements() method in the simpleexecutior class does nothing but returns an empty collection object.

 @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    return Collections.emptyList();
  }
4, Reuseexecutior class

                     . In reuseexecution class, the implementation of three methods, doUpdate(), doQuery(), doQueryCursor(), is similar to that of simpleexecution class. The main difference is the implementation of prepareStatement().

  1. prepareStatement() method
       in the prepareStatement() method, first try to get the cached Statement object from statementMap, otherwise re create it and put it into statementMap variable for caching.
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
      stmt = getStatement(sql);
      applyTransactionTimeout(stmt);
    } else {
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection, transaction.getTimeout());
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
  }
  1. doFlushStatements() method
    In this method, the closing of cached Statement object is realized.
@Override
  public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
    for (Statement stmt : statementMap.values()) {
      closeStatement(stmt);
    }
    statementMap.clear();
    return Collections.emptyList();
  }

Because of the length, the BatchExecutor class and the cacheexecutor class are analyzed in the.

Published 50 original articles, won praise 3, visited 3773
Private letter follow

Tags: SQL Database Stored Procedure Mybatis

Posted on Wed, 19 Feb 2020 19:29:20 -0800 by bigsid