This article source code from mybatis-spring-boot-starter 2.1.2 version

The execution of SQL statements involves various components, of which the most important are Executor, StatementHandler, ParameterHandler, and ResultSetHandler.

The Executor object is created when the Configuration object is created and cached in the Configuration object. It manages level 1 and level 2 caches and provides operations related to transaction management. The primary function of an Executor object is to call StatementHandler to access the database and store the query results in a cache, if configured. The StatementHandler uses the ParammeterHandler to bind the SQL arguments. The StatementHandler uses the java.sql.Statement object to execute the SQL Statement and obtain the ResultSet. Finally, the mapping of the result set is completed through ResultSetHandler, and the object is obtained and returned.

Executor

1.1 class diagram

Each SqlSession has an Executor object, which is responsible for adding, deleting, modifying, and querying. It can be interpreted as a encapsulated version of a JDBC Statement. An Executor is an Executor, and the Executor is an Executor.

At the top of the hierarchy, as shown in the figure above, is the Executor Executor, which has two implementation classes, BaseExecutor and CachingExecutor.

  • BaseExecutor

BaseExecutor is an abstract class that implements an interface through abstraction. BaseExecutor is the default implementation of Executor. It implements most of the functions defined by the Executor interface. There are four subclasses of ️BaseExecutor, namely SimpleExecutor, ReuseExecutor, CloseExecutor and BatchExecutor.

  1. SimpleExecutor

The Statement object is the default executable used in MyBatis. The Statement object is opened every time an update or select is executed. Close the Statement object (either a Statement or a PreparedStatment object)

  1. ReuseExecutor

The reusable executor, by reuse, means the reuse of the Statement. It uses an internal Map to cache all created statements. Each time a SQL command is executed, it checks whether a Statement object based on the SQL Statement exists. If a Statement object exists and the connection has not been closed, continue to use the previous Statement object and cache it. Since every SqlSession has a new Executor object, the Statement scope cached on ReuseExecutor is the same SqlSession.

  1. CloseExecutor

Represents a closed Executor execution period

  1. BatchExecutor

Batch executor, used to output multiple SQL to the database at once

  • CachingExecutor

The cache executor queries the result from the cache and returns the previous result if it exists. If not, delegate to an Executor delegate from the database. The delegate can be any of the above.

1.2 Executor selection and Creation

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 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

SqlSessionFactory through Configuration to create the sqlSession, Executor is initialized at this time, we’ll look at the Configuration. The newExecutor () this method:

  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 {
    // Use a simple actuator if not specified
      executor = new SimpleExecutor(this, transaction);
    }
    // If cacheEnabled is true, CachingExecutor is created and then the Executor created above is held internally
    // If cacheEnabled defaults to true, the default Executor created is CachingExecutor with SimpleExecutor wrapped inside.
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    / / using InterceptorChain pluginAll create a proxy object for the executor. Mybatis plugin mechanism.
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
Copy the code

The ExecutorType can be assigned using XML tags and the JavaApi, and defaults to executortype.simple.

The Mybatis plugin mechanism will be covered in a series of articles, but not here.

1.3 Executor Execution Process

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      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

If cacheEnabled defaults to true, the default Executor will be CachingExecutor and will be wrapped around SimpleExecutor. So this executor is still CachingExecutor, so let’s look at the implementation of CachingExecutor

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
      // Whether there is a cache
    Cache cache = ms.getCache();
    if(cache ! =null) {
    // Create a cache
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
            // Execute the SimpleExecutor query method
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        returnlist; }}// Execute the SimpleExecutor query method
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
Copy the code

The delegate here is SimpleExecutor, which means that the Query implementation of SimpleExecutor will be executed eventually. At this point, the work done by the Executor is complete, and the Executor hands off the rest of the work to continue. Let’s get to know StatementHandler.

StatementHandler

StatementHandler is responsible for manipulating Statement objects and interacting with the database. Let’s review native JDBC:

        //1 Registers the driver
        Class.forName("com.mysql.jdbc.Driver");
        //2 get the connection
        String url = "jdbc:mysql://localhost:3306/test";
        Connection conn = DriverManager.getConnection(url,"root"."root");
        //3 Get the statement executor
        Statement st = conn.createStatement();
        //4 Execute the SQL statement
        ResultSet rs = st.executeQuery("select * from role");
        //5 Process the result set
        while(rs.next()){
            // Get a row of data
            Integer id = rs.getInt("id");
            String name = rs.getString("name");
            System.out.println(id + "," + name);
        }
        //6 Release resources
        rs.close();
        st.close();
        conn.close();
Copy the code

JDBC creates a Statement object using the java.sql.Connection object. The execute method of the Statement object is the entry point for executing SQL statements. The StatementHandler for Mybtais is used to manage Statement objects.

2.1 class diagram

BaseStatementHandler
RoutingStatementHandler
SimpleStatementHandler
PreparedStatementHandler
CallableStatementHandler

Let’s start by looking at the methods defined by StatementHandler:

ublic interface StatementHandler {
    // Create a Statement object. This method creates a Statement object through the Connection object.
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
    // Parameterize Statement objects, especially PreapreStatement objects.
  void parameterize(Statement statement)
      throws SQLException;
    // Execute SQL in batches
  void batch(Statement statement)
      throws SQLException;
    / / update
  int update(Statement statement)
      throws SQLException;
    / / query
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
    // query by subscript
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
    // Get the SQl statement
  BoundSql getBoundSql(a);
    // Get the parameter handler
  ParameterHandler getParameterHandler(a);

}
Copy the code
  • BaseStatementHandler

StatementHandler is an abstract class that simplifies the implementation of the StatementHandler interface. It is part of the adapter design pattern and has three main implementation classes:

  1. SimpleStatementHandler

The java.sql.Statement object creates a processor that manages the Statement object and pushes SQL statements to the database that do not require precompilation.

  1. PreparedStatementHandler

Java. SQL. PrepareStatement object create processor, management Statement object and to the data needed to push a precompiled SQL Statement.

Note: The difference between SimpleStatementHandler and PreparedStatementHandler is whether the SQL statement contains variables. Whether parameters are passed in externally. SimpleStatementHandler is used to execute SQL without any arguments being passed in. PreparedStatementHandler requires pre-parameter binding and assignment of externally passed variables and parameters.

  1. CallableStatementHandler

Java. SQL. The CallableStatement object create processor, management Statement object and call a stored procedure in the database.

  • RoutingStatementHandler
 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: "+ ms.getStatementType()); }}Copy the code

Like CachExecutor, the RoutingStatementHandler does not use the Statement object, but instead creates a proxy based on StatementType that represents the three implementation classes of the Handler. The StatementHandler interface object used when working with MyBatis is actually the RoutingStatementHandler object.

2.2 StatementHandler selection and creation

**queryFromDatabase() doQuery()** queryFromDatabase();

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

2.2.1 configuration. NewStatementHandler

 public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    //
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // Plugin mechanism
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }
Copy the code

It creates a RoutingStatementHandler object, which, as we said, creates the corresponding Statement object based on StatementType. StatementType is an attribute of MappedStatement, which defaults to statementType.prepared when bulid is created. PreparedStatementHandler calls the constructor of its parent class BaseStatementHandlerde when it is built. ParameterHandler and ResultSetThandler are also created here, so let’s take a look.

2.2.2 BaseStatementHandler

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;

    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;
    / / build parameterHandler
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    / / build resultSetHandler
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
Copy the code

ParameterHandler

3.1 class diagram

  • GetParameterObject: Reads parameters

  • SetParameters: Used to assign values to parameters in a PreparedStatement

3.2 Creating a ParameterHandler

public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
    parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
    return parameterHandler;
  }
Copy the code

ResultSetHandler

4.1 class diagram

ResultSetHandler
DefaultResultSetHandler

  • Process the result set generated after the Statement is executed and generate a result list.

  • Handles the output parameters of the stored procedure after execution

4.2 Creation of ResultSetHandler

public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {
    ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
    resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
    return resultSetHandler;
  }
Copy the code

This article is just an introduction to the components of Mybatis that execute SQL. We will explain the process of executing SQL in detail later.