Mybatis source learning (25)-StatementHandler parsing

Posted by robsgaming on Sat, 18 Jan 2020 07:07:33 +0100

I. Introduction

The    StatementHandler is one of the most core interfaces of Mybatis. He has completed the work related to the interaction between Mybatis and database (the interaction between Mybatis and JDBC on Statement). StatementHandler is mainly used for:

  • Create Statement object
  • Binding arguments for SQL statements
  • Execute select, insert, update, delete and other types of SQL statements
  • Execute SQL statements in batch
  • Result set mapping to result object

The StatementHandler interface is defined as follows:

public interface StatementHandler {
	/**
	 * Get a Statement from the connection
	 * @param connection
	 * @param transactionTimeout
	 * @return
	 * @throws SQLException
	 */
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;
  /**
   * Binding the arguments required for statement execution
   * @param statement
   * @throws SQLException
   */
  void parameterize(Statement statement)
      throws SQLException;
  /**
   * Batch execution of SQL statements
   * @param statement
   * @throws SQLException
   */
  void batch(Statement statement)
      throws SQLException;
  /**
   * Execute update/insert/delete statement
   * @param statement
   * @return
   * @throws SQLException
   */
  int update(Statement statement)
      throws SQLException;
  /**
   * Execute select statement
   * @param <E>
   * @param statement
   * @param resultHandler
   * @return
   * @throws SQLException
   */
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;
  /**
   * Execute select statement
   * @param <E>
   * @param statement
   * @return
   * @throws SQLException
   */
  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;
  /**
   * Get the BoundSql object corresponding to the record SQL statement
   * @return
   */
  BoundSql getBoundSql();
  /**
   * Get the ParameterHandler object used
   * @return
   */
  ParameterHandler getParameterHandler();

}
2, StatementHandler interface and its implementation class

   in Mybatis, StatementHandler interface provides an abstract class BaseStatementHandler and a direct implementation class RoutingStatementHandler, while the abstract class BaseStatementHandler has three implementation subclasses: SimpleStatementHandler, PreparedStatementHandler, CallableStatementHandler, etc. The functions of each implementation class are as follows:

  • BaseStatementHandler abstract class, mainly implements public methods
  • SimpleStatementHandler implementation class, mainly corresponding to the Statement interface in JDBC, for simple SQL processing;
  • The PreparedStatementHandler implementation class, which mainly corresponds to the PreparedStatement in JDBC and handles the interface of precompiled SQL;
  • The CallableStatementHandler implementation class, mainly corresponding to the CallableStatement in JDBC, is used to execute the interface related to the stored procedure;
  • The RoutingStatementHandler directly implements the classes. The routing classes of the above three implementation classes have no actual operation, but are responsible for the creation and invocation of the above three statementhandlers.

Class diagram of StatementHandler interface:

3, Implementation class RoutingStatementHandler

   RoutingStatementHandler plays the role of router in StatementHandler's architecture.
The RoutingStatementHandler creates the corresponding
StatementHandler interface implementation of. The specific implementation is as follows:

public class RoutingStatementHandler implements StatementHandler {
  /**
   * Instance object of a real StatementHandler
   */
  private final StatementHandler delegate;
  /**
   * According to the StatementType type, select to create a real StatementHandler instance and assign it to the variable delegate
   * @param executor
   * @param ms
   * @param parameter
   * @param rowBounds
   * @param resultHandler
   * @param boundSql
   */
  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://If the type is not specified, an exception will be thrown directly
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }
  
  //The following methods are all implemented through the real StatementHandler object
  ......
  
}
4, Abstract class BaseStatementHandler

Abstract class BaseStatementHandler is the parent class of three implementation classes: SimpleStatementHandler, PreparedStatementHandler and CallableStatementHandler. It mainly implements some common methods.

1, variables

The variables defined in BaseStatementHandler and their meanings are as follows:

//BaseStatementHandler.java
/**
   * Global variable, the core configuration object of Mybatis
   */
  protected final Configuration configuration;
  /**
   * Tool class, which is used to create an object and obtain the instance object through configuration.
   */
  protected final ObjectFactory objectFactory;
  /**
   * Register of type processor, which is used to get the qualified type processor, and obtain the instance object through configuration.
   */
  protected final TypeHandlerRegistry typeHandlerRegistry;
  /**
   * Record the ResultSetHandler object used. Its main function is to map the result set to the result object
   */
  protected final ResultSetHandler resultSetHandler;
  /**
   * Record the ParameterHandler object used. The main function of ParameterHandler is to bind parameters for SQL statements, that is
   * Replace the "? In the SQL statement with the arguments passed in placeholder
   */
  protected final ParameterHandler parameterHandler;
  /**
   * Record the Executor object executing the SQL statement
   */
  protected final Executor executor;
  /**
   * Record the MappedStatement object corresponding to the SQL statement
   */
  protected final MappedStatement mappedStatement;
  /**
   * RowBounds The offset and limit set by the user are recorded to locate the start and end positions of the mapping in the result set
   */
  protected final RowBounds rowBounds;
  /**
   * Record the BoundSql object corresponding to the SQL statement
   */
  protected BoundSql boundSql;
2. Constructor

   in the constructor, in addition to the initial fields through the parameters of the constructor, there are also parameters initialized through the global variable configuration. For example, typeHandlerRegistry and objectFactory are parsed and stored in the global variable configuration when the configuration file is initialized, and parameterHandler variable is through the newparameterhandler of the configuration () method is initialized, and the resultSetHandler variable is initialized through the newResultSetHandler() method of configuration. In addition, when the boundSql parameter is empty, first initialize the primary key of SQL through the generateKeys() method, and then get the corresponding boundSql instance according to the mappedStatement.getBoundSql() method.

//BaseStatementHandler.java
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
      //Call KeyGenerator.processBefore() method to initialize the primary key of SQL statement
      generateKeys(parameterObject);
      //Initialize the boundSql variable according to the parameter
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }
3. prepare() method

The   prepare() method is used to obtain the corresponding Statement instance object according to the Connection connection. In this method, first initialize the Statement object by calling the instantiateStatement() abstract method, and then set the timeout through setStatementTimeout(),
The setFetchSize() method sets parameters such as fetchSize. Among them, the abstract method of instantiateStatement() is defined by BaseStatementHandler and implemented by subclass. In this way, the operation of initializing Statement object can be created by subclass according to their own requirements, and the general configuration related to parameter setting is completed by abstract class (parent class).

//BaseStatementHandler.java
@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      //Initialize the java.sql.Statement object by calling the instantiateStatement() abstract method
      statement = instantiateStatement(connection);
      //Set timeout
      setStatementTimeout(statement, transactionTimeout);
      //Set the fetchSize parameter, etc
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }
//BaseStatementHandler.java
 protected void setStatementTimeout(Statement stmt, Integer transactionTimeout) throws SQLException {
    Integer queryTimeout = null;
    if (mappedStatement.getTimeout() != null) {
      queryTimeout = mappedStatement.getTimeout();
    } else if (configuration.getDefaultStatementTimeout() != null) {
      queryTimeout = configuration.getDefaultStatementTimeout();
    }
    if (queryTimeout != null) {
      stmt.setQueryTimeout(queryTimeout);
    }
    StatementUtil.applyTransactionTimeout(stmt, queryTimeout, transactionTimeout);
  }
//BaseStatementHandler.java
protected void setFetchSize(Statement stmt) throws SQLException {
    Integer fetchSize = mappedStatement.getFetchSize();
    if (fetchSize != null) {
      stmt.setFetchSize(fetchSize);
      return;
    }
    Integer defaultFetchSize = configuration.getDefaultFetchSize();
    if (defaultFetchSize != null) {
      stmt.setFetchSize(defaultFetchSize);
    }
  }
5, Implementation class SimpleStatementHandler

                     .

  1. parameterize() method
    Empty method, no processing.
  2. instantiateStatement() method
    In this method, the Statement object is instantiated through the createStatement() method of connection.
//SimpleStatementHandler.java
@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() != null) {
    	//Set whether the result set can be scrolled, whether the cursor can be moved up and down, and whether the result set can be updated
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.createStatement();
    }
  }
  1. query(), queryCursor() methods
    query() and queryCursor() are similar in logic. First get the SQL statement to be executed, then execute the SQL through the statement.execute() method, and finally process the result set through the handleResultSets() method or handleCursorResultSets() method of resultSetHandler.
//SimpleStatementHandler.java
 @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    //Call Statement.executor() method to execute SQL statement
    statement.execute(sql);
    //Mapping result sets to result objects
    return resultSetHandler.<E>handleResultSets(statement);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleCursorResultSets(statement);
  }
  1. batch() method
//SimpleStatementHandler.java
 @Override
  public void batch(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.addBatch(sql);
  }
  1. update() method
    This method is mainly used to complete the execution of insert, update, delete and other SQL statements. This method needs to deal with the relevant logic of generating the primary key. First, the statement.execute() method, and then execute the corresponding processAfter() method according to the type of keyGenerator.
//SimpleStatementHandler.java
@Override
  public int update(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
    	//Call Statement.executor() method to execute SQL statement
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      rows = statement.getUpdateCount();
      //Add the primary key generated by the database to the parameterObject
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      statement.execute(sql);
      rows = statement.getUpdateCount();
      //Execute the SQL statement configured in the < selectkey > node to obtain the primary key generated by the database and add it to the parameterObject
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }
6, Implementation class PreparedStatementHandler

                      .

  1. parameterize() method
    The parameter binding of the SQL statement is completed through the ParameterHandler.setParameters() method.
//PreparedStatementHandler.java
@Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
  1. instantiateStatement() method
    Directly call the prepareStatement() method of JDBC Connection to create the PreparedStatement object. When the type returned by mappedStatement.getKeyGenerator() is Jdbc3KeyGenerator, the PreparedStatement object is initialized based on the fields contained in keyColumnNames.
//PreparedStatementHandler.java
@Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
    	  //Return the primary key generated by the database
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
    	  //After the insert statement is executed, the column specified by keyColumnNames will be returned
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
    	//Set whether the result set can be scrolled, whether its cursor can be moved up and down, and whether the result set can be updated
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
  }
  1. query(), queryCursor() methods
    query() and queryCursor() are similar in logic. First, convert the parameter statement to PreparedStatement type, then execute the ps.execute() method, and finally process the result set through the handleResultSets() method of resultSetHandler or handleCursorResultSets() method.
//PreparedStatementHandler.java
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleCursorResultSets(ps);
  }
  1. batch() method
//PreparedStatementHandler.java
@Override
  public void batch(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.addBatch();
  }
  1. update() method
    This method is mainly used to complete the execution of insert, update, delete and other SQL statements. First, the statement type is forced to PreparedStatement, then the ps.execute() method is executed, and then the keyGenerator.processAfter() is executed according to the parameters to process the relevant logic of generating the primary key.
//PreparedStatementHandler.java
@Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }
7, Implementation class CallableStatementHandler

The     CallableStatementHandler implementation class, which mainly corresponds to the CallableStatement in JDBC, is used to execute the interface related to the stored procedure. This class is similar to the logic of the implementation class PreparedStatementHandler, which is not repeated here. Just analyze the special parameterize() method and the registerOutputParameters() method it calls.

  1. parameterize() method
    In the parameterize() method, the statement parameter is first processed through the registerOutputParameters() method, and then the parameterHandler.setParameters() method is called to set the parameter.
//CallableStatementHandler.java
  @Override
  public void parameterize(Statement statement) throws SQLException {
    registerOutputParameters((CallableStatement) statement);
    parameterHandler.setParameters((CallableStatement) statement);
  }
  1. registerOutputParameters() method
    Process the output parameters of the stored procedure. When we introduce parameterHandler and resultSetHandler later, we will explain them in detail.
//CallableStatementHandler.java
private void registerOutputParameters(CallableStatement cs) throws SQLException {
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    for (int i = 0, n = parameterMappings.size(); i < n; i++) {
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
        if (null == parameterMapping.getJdbcType()) {
          throw new ExecutorException("The JDBC Type must be specified for output parameter.  Parameter: " + parameterMapping.getProperty());
        } else {
          if (parameterMapping.getNumericScale() != null && (parameterMapping.getJdbcType() == JdbcType.NUMERIC || parameterMapping.getJdbcType() == JdbcType.DECIMAL)) {
            cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getNumericScale());
          } else {
            if (parameterMapping.getJdbcTypeName() == null) {
              cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE);
            } else {
              cs.registerOutParameter(i + 1, parameterMapping.getJdbcType().TYPE_CODE, parameterMapping.getJdbcTypeName());
            }
          }
        }
      }
    }
  }
Published 46 original articles, won praise 3, visited 2589
Private letter follow

Topics: SQL Java JDBC Mybatis