Mybatis source code mybatis excel framework

As we introduced in the previous chapter, Mybatis will convert all database operations into iBatis programming model, and operate the database through the facade class SqlSession. However, when we go deep into the source code of SqlSession, we will find that SqlSession does nothing, and it entrusts you with the database operations, as shown in the figure below:

Excel framework class diagram

BaseExecutor

In BaseExecutor, the basic implementation of Executor is defined, such as query level 1 cache, transaction processing and other unchanging parts, operation database and other changing parts are implemented by subclasses, and template design mode is used. Let's look at the source code of 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);
}

/**
 * All query operations are finally handled by this method
 */
@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()) {
    // Clear local cache
    clearLocalCache();
  }
  List<E> list;
  try {
    // Query level plus one
    queryStack++;
    // Query level 1 cache
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      // Handling OUT parameters of stored procedures
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      // Cache misses, querying databases
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    // Query level minus one
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // Add cache placeholder
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // Delete cache placeholder
    localCache.removeObject(key);
  }
  // Add query results to local cache
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    // Cache parameters if stored procedure
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

In the queryFromDatabase() method, we can see that doQuery uses the template method, and the specific logic is implemented by the subclass. The advantage of doing this is that the subclass only cares about the part of the program changes, and the other unchanged parts are implemented by the parent class. It improves the reusability and expansibility of code.

SimpleExecutor

Common executor, which is used by default by Mybatis, to create a new Statement every time. Let's look at the source code of the query method:

@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
    // Get Mybatis configuration class
    Configuration configuration = ms.getConfiguration();
    // Get StatementHandler according to configuration class
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    // Create Statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.query(stmt, resultHandler);
  } finally {
    closeStatement(stmt);
  }
}

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  // Get Connection connection
  Connection connection = getConnection(statementLog);
  // Get Statement according to Connection
  stmt = handler.prepare(connection, transaction.getTimeout());
  // Setting parameters
  handler.parameterize(stmt);
  return stmt;
}

With stmt = handler.prepare(connection, transaction.getTimeout()); method, we can see that each time a new Statement is created.

ReuseExecutor

The executors that can be reused are statements. Internally, a Map is used to cache Statement objects with sql statements as the key. As long as the connection is kept open, Statement can be reused.

Because every new SqlSession has a new Executor object, the scope of the Statement we cache on reuseexecution is the same SqlSession, so in fact, this cache is not very useful. Let's take a look at getting the Statement source code directly. The other parts are the same as the simple Executor query method.

private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  BoundSql boundSql = handler.getBoundSql();
  String sql = boundSql.getSql();
  if (hasStatementFor(sql)) {
    // Get reused Statement
    stmt = getStatement(sql);
    applyTransactionTimeout(stmt);
  } else {
    // Create a new Statement and cache it
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    putStatement(sql, stmt);
  }
  handler.parameterize(stmt);
  return stmt;
}

private boolean hasStatementFor(String sql) {
  try {
    // Judge whether the Statement is cached according to the sql and whether the Connection is closed
    return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();
  } catch (SQLException e) {
    return false;
  }
}

private Statement getStatement(String s) {
  return statementMap.get(s);
}

private void putStatement(String sql, Statement stmt) {
  statementMap.put(sql, stmt);
}

BatchExecutor

Batch executor, which encapsulates the jdbc statement.addBatch(String sql) and statement.executeBatch(); to realize batch processing. Transactions for this actuator can only be in manual commit mode.

We usually perform batch processing in the way of sql splicing.

When performing batch update, it is recommended not to update too much data at a time. If the amount of updated data is large, it can be performed in sections.

CachingExecutor

If the second level cache is enabled, Mybatis will use the cachexecution executor, which uses the decorator mode.

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  throws SQLException {
  // Get L2 cache
  Cache cache = ms.getCache();
  if (cache != null) {
    // Refresh cache
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      // Cache lookup
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        // Call decorated method
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        // Put data in cache
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  // Cache not found, call decorated method directly
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

Through the source code, we find that the whole query process has become L2 - > L1 - > dB. The query process of the caching executor increases the query operation of the secondary cache.

In practice, we seldom use the second level cache of Mybatis. If we want to do the second level cache, we recommend using the third-party cache framework directly at the service level. It is recommended to use the third-party cache framework Layered cache: a multi-level cache framework for monitoring It is more convenient and flexible to use. The query process is L1 - > L2 - > dB.

summary

  • BaseExecutor: the template method pattern is used to define the basic implementation of Executor. It is an abstract class and cannot provide services directly to the outside world.
  • Simple executor: a common executor, which is used by Mybatis by default. Each time a Statement is created.
  • Reuse executor: the executor of Statement can be reused, but the Statement cache is only valid in one SqlSession. We seldom perform the same query operations in one SqlSession, so the performance improvement is not great.
  • BatchExecutor: batch executor
  • Caching executor: second level cache executor. Using decorator mode, the whole query process becomes L2 - > L1 - > dB. It is recommended to use the third-party caching framework directly, such as: Layered cache: a multi-level cache framework for monitoring.

Tags: Programming SQL Mybatis Database

Posted on Tue, 05 Nov 2019 22:58:20 -0500 by elearnindia