preface

Using the MapperMethod#execute() method, we can see that there are more methods to execute the SELECT query:

  • executeWithResultHandler
  • executeForMany
  • executeForMap
  • executeForCursor

These methods internally call some select* methods of SqlSession. The return value types of these methods are different, so there needs to be a special handling method for each return type. Take the selectList() method, which returns a value of type List. However, if the Mapper or Dao interface method returns a value of type array or Set, it is not appropriate to directly return the result of type List to Mapper/Dao. Methods like execute* simply encapsulate methods like select*.

selectOne

SelectList () is called inside the sqlssession #selectOne() method.

// SqlSession
public <T> T selectOne(String statement, Object parameter) {
  List<T> list = this.selectList(statement, parameter);
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    // Common exceptions. If the query result is greater than 1, it will be thrown
    throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
  } else {
    return null; }}Copy the code

selectList

Query responsibility is eventually delegated to the Executor# Query () method.

public <E> List<E> selectList(String statement) {
  // Call overloaded methods
  return this.selectList(statement, null);
}

public <E> List<E> selectList(String statement, Object parameter) {
  // Call overloaded methods
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

// selectList final implementation method
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
    / / get MappedStatement
    MappedStatement ms = configuration.getMappedStatement(statement);
    // Call the Executor implementation class'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

Executor is an interface for executing defined SQL statements. Its implementation class diagram:

Create the default Executor

Executor is a member variable in DefaultSqlSession. Trace the DefaultSqlSessionFactory#openSession() method, which creates the CachingExecutor instance object by default. The CachingExecutor class is a decorator class that adds level 2 caching to the target Executor. The target Executor defaults to SimpleExecutor

// DefaultSqlSessionFactory
public SqlSession openSession(a) {
  // ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  // The default executor type in Configuration is SIMPLE
  // Create the default SimpleExecutor object
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
}

// DefaultSqlSessionFactory
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 specified Executor instance object based on the argument
    final Executor executor = configuration.newExecutor(tx, execType);
    // Returns the DefaultSqlSession instance object
    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(); }}// Configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  if (ExecutorType.BATCH == executorType) {
    executor = new BatchExecutor(this, transaction);
  } else if (ExecutorType.REUSE == executorType) {
    executor = new ReuseExecutor(this, transaction);
  } else {
    // SimpleExecutor is created by default
    executor = new SimpleExecutor(this, transaction);
  }
  
  // Defaults to true
  if (cacheEnabled) {
    // Decorator class to add level 2 caching to the target Executor
    executor = new CachingExecutor(executor);
  }
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
Copy the code

CachingExecutor#query

The query functionality of the sqlssession #selectList() method will eventually be delegated to the CachingExecutor# Query () method. The CachingExecutor class actually adds level 2 caching.

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

// CachingExecutor
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  // Get the cache from MappedStatement
  Cache cache = ms.getCache();
  // If no cache is configured in the XML mapping file, cache = null
  if(cache ! =null) {
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        // If the cache misses, the decorator class's query() method is called
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); 
      }
      returnlist; }}// Call the decorator class's query() method
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
Copy the code

The final execution of the query is delegated to the target Executor, which is the decorator design pattern. The query() method called here is actually the query() method of the target Executor’s BaseExecutor superclass.

BaseExecutor#query

The query() method of the parent class performs the generic function of querying the result set from the level-1 cache, or calling queryFromDatabase() if the cache misses.

// BaseExecutor
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++;
    // Get results from level 1 cache
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if(list ! =null) {
      // Execute stored procedure logic
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      // If level 1 cache does not match, query from databaselist = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); }}finally {
    queryStack--;
  }
  if (queryStack == 0) {
    Lazily loads nested query results from level 1 cache
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    
    deferredLoads.clear();
    if(configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { clearLocalCache(); }}return list;
}
Copy the code

BaseExecutor#queryFromDatabase

The queryFromDatabase() method still ends up calling the doQuery() method to query and add the returned result set to the level-1 cache. But the doQuery() method is an abstract method in the BaseExecutor class, so the final query function is implemented by a different subclass.

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // Add a placeholder to the level 1 cache
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    // Call doQuery() to query
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // Remove the placeholder
    localCache.removeObject(key);
  }
  // Cache query results
  localCache.putObject(key, list);
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}
Copy the code

SimpleExecutor#doQuery

At this point it becomes clear that the entire query function is done by the StatementHandler class. And it is clear that the prepareStatement() method creates the Statment object. This is already the realm of JDBC.

// SimpleExecutor
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 the Statement
    stmt = prepareStatement(handler, ms.getStatementLog());
    // Perform query operations
    return handler.query(stmt, resultHandler);
  } finally {
    / / close the StatementcloseStatement(stmt); }}Copy the code

StatementHandler is an interface, There are three main implementation classes: SimpleStatementHandler, PreparedStatementHandler, and CallableStatementHandler, which correspond to statementType STATEMENT. PREPARED and CALLABLE

StatementType | optional STATEMENT, PREPARED or CALLABLE. This will make MyBatis use Statement, PreparedStatement, or CallableStatement, respectively. The default value is PREPARED.

PreparedStatementHandler#query

At this point, the whole call is kind of over. The method is to execute the SQL and process the result set.

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
  PreparedStatement ps = (PreparedStatement) statement;
  / / SQL execution
  ps.execute();
  // Process the execution result
  return resultSetHandler.handleResultSets(ps);
}
Copy the code