Mybatis核心處理層源碼閱讀分析(StatementHandler系列組件)

StatementHandler接口是MyBatis的核心接口之一,它完成了MyBatis中最核心的工作,也是Executor 接口實現的基礎。

StatementHandler接口中的功能很多,例如創建Statement對象,爲SQL語句綁定實參,執行select、insert、update、delete等多種類型的SQL語句,批量執行SQL語句,將結果集映射成結果對象。

public interface StatementHandler {

  // 從連接中獲取一個Statement
  Statement prepare(Connection connection, Integer transactionTimeout)
      throws SQLException;

  // 綁定statement執行時所需的實參
  void parameterize(Statement statement)
      throws SQLException;

  // 批量執行SQL語句
  void batch(Statement statement)
      throws SQLException;

  // 執行update/insert/delete語句
  int update(Statement statement)
      throws SQLException;

  // 執行select語句
  <E> List<E> query(Statement statement, ResultHandler resultHandler)
      throws SQLException;

  <E> Cursor<E> queryCursor(Statement statement)
      throws SQLException;

  BoundSql getBoundSql();

  // 獲取參數處理器
  ParameterHandler getParameterHandler();

}

RoutingStatementHandler

RoutingStatementHandler使用了策略模式,RoutingStatementHandler是策略類,而SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler則是實現了具體算法的實現類,RoutingStatementHandler對象會根據MappedStatement對象的StatementType屬性值選擇使用相應的策略去執行。

public class RoutingStatementHandler implements StatementHandler {

  // 持有的真正實現StatementHandler接口功能的對象
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // RoutingStatementHandler的作用就是根據ms的配置,生成一個相對應的StatementHandler對象
    // 並設置到持有的delegate屬性中,本對象的所有方法都是通過調用delegate的相應方法實現的
    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());
    }

  }

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    delegate.batch(statement);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    return delegate.update(statement);
  }

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    return delegate.queryCursor(statement);
  }

  @Override
  public BoundSql getBoundSql() {
    return delegate.getBoundSql();
  }

  @Override
  public ParameterHandler getParameterHandler() {
    return delegate.getParameterHandler();
  }
}

BaseStatementHandler

看它以Base開頭,就可以猜到 它是一個實現了StatementHandler接口的抽象類,這個類只提供了一些參數綁定相關的方法,並沒有實現操作數據庫的方法。

public abstract class BaseStatementHandler implements StatementHandler {

  // 持有的這些屬性都是通過構造方法完成初始化的,typeHandlerRegistry、
  // objectFactory、parameterHandler等則是通過configuration屬性獲得的
  protected final Configuration configuration;
  protected final ObjectFactory objectFactory;
  protected final TypeHandlerRegistry typeHandlerRegistry;
  protected final ResultSetHandler resultSetHandler;
  // parameterHandler的功能主要是爲SQL語句綁定實參,也就是使用傳入的實參
  // 替換SQL語句中的佔位符"?"
  protected final ParameterHandler parameterHandler;

  // 用來執行SQL語句的執行器
  protected final Executor executor;
  protected final MappedStatement mappedStatement;
  // 記錄了用戶設置的offset和limit,用於在結果集中定位
  // 映射的起始位置和結束位置
  protected final RowBounds rowBounds;

  protected BoundSql boundSql;

  // 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
      // 其中調用了KeyGenerator的processBefore()方法
      // 用於初始化SQL語句的主鍵
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;

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

  protected void generateKeys(Object parameter) {
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    ErrorContext.instance().store();
    keyGenerator.processBefore(executor, mappedStatement, null, parameter);
    ErrorContext.instance().recall();
  }

  @Override
  public BoundSql getBoundSql() {
    return boundSql;
  }

  @Override
  public ParameterHandler getParameterHandler() {
    return parameterHandler;
  }

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      // 這是一個抽象方法,用於初始化java.sql.Statement對象
      statement = instantiateStatement(connection);
      // 爲Statement對象設置超時時間及fetchSize
      setStatementTimeout(statement, transactionTimeout);
      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);
    }
  }

  protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

  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);
  }

  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);
    }
  }

  protected void closeStatement(Statement statement) {
    try {
      if (statement != null) {
        statement.close();
      }
    } catch (SQLException e) {
      //ignore
    }
  }

}

BaseStatementHandler主要實現了StatementHandler接口中的prepare()方法,BaseStatementHandler依賴兩個重要的組件,ParameterHandler和ResultSetHandler。

ParameterHandler系列組件

我們要執行的SQL語句中可能包含佔位符"?",而每個"?"都對應了BoundSql中parameterMappings集合中的一個元素,在該ParameterMapping對象中記錄了對應的參數名稱以及該參數的相關屬性。ParameterHandler接口定義了一個非常重要的方法setParameters(),該方法主要負責調用PreparedStatement的set*()系列方法,爲SQL語句綁定實參。MyBatis只爲ParameterHandler接口提供了唯一一個實現類DefaultParameterHandler。

public interface ParameterHandler {

  // 獲取用戶傳入的實參對象
  Object getParameterObject();

  // 本方法主要負責調用PreparedStatement.set*()方法,爲SQL語句綁定實參。
  void setParameters(PreparedStatement ps)
      throws SQLException;

}


public class DefaultParameterHandler implements ParameterHandler {

  // 管理mybatis中所有的TypeHandler對象
  private final TypeHandlerRegistry typeHandlerRegistry;

  // 其中記錄了SQL節點相應的配置信息
  private final MappedStatement mappedStatement;
  // 用戶傳入的實參對象
  private final Object parameterObject;
  // 其中記錄了要執行的SQL語句,及參數信息
  private final BoundSql boundSql;
  private final Configuration configuration;

  // 構造方法主要爲持有的屬性 進行初始化
  public DefaultParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
    this.mappedStatement = mappedStatement;
    this.configuration = mappedStatement.getConfiguration();
    this.typeHandlerRegistry = mappedStatement.getConfiguration().getTypeHandlerRegistry();
    this.parameterObject = parameterObject;
    this.boundSql = boundSql;
  }

  @Override
  public Object getParameterObject() {
    return parameterObject;
  }

  // 爲PreparedStatement對象要執行的SQL語句中的佔位符 設置對應的參數值
  @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 獲取參數列表
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 過濾掉存儲過程中的輸出參數
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          // 記錄綁定的實參
          Object value;
          // 獲取參數對應的屬性名
          String propertyName = parameterMapping.getProperty();
          // 根據屬性名 獲取 實參值
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          // 整個實參爲空
          } else if (parameterObject == null) {
            value = null;
          // 如果實參可以直接通過TypeHandler轉換成JdbcType
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            // 獲取對象中相應的屬性值 或查找Map對象中的值
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 獲取當前parameterMapping中的TypeHandler對象 及JdbcType對象
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            // TypeHandler的setParameter()方法會調用PreparedStatement對象的
            // set*()系列方法,爲SQL語句綁定相應的實參
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

}

爲SQL語句綁定完實參之後,就可以調用Statement對象 相應的execute方法,將SQL語句交給數據庫執行了。

SimpleStatementHandler

SimpleStatementHandler繼承了BaseStatementHandler抽象類。其底層使用java.sql.Statement來完成數據庫的相關操作,所以SQL語句中不存在佔位符,所以SimpleStatementHandler的parameterize()方法是空實現。SimpleStatementHandler的instantiateStatement()方法直接通過JDBC Connection創建Statement對象。

public class SimpleStatementHandler extends BaseStatementHandler {

  // 構造方法主要用於屬性的初始化
  public SimpleStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  // 直接通過Connection創建Statement對象
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      // 如果結果集類型是DEFAULT默認的,則直接用connection創建Statement對象
      return connection.createStatement();
    } else {
      // 否則,設置結果集類型,設置結果集 只讀
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

  // 上面創建的Statement對象會被本方法用於完成數據庫查詢操作
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    // 獲取SQL語句
    String sql = boundSql.getSql();
    // 發送請求 執行SQL語句
    statement.execute(sql);
    // 從statement中獲取結果集,並進行映射處理
    return resultSetHandler.handleResultSets(statement);
  }

  // 下面的batch()及queryCursor()方法的實現與上面的query()方法非常類似
  @Override
  public void batch(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.addBatch(sql);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.handleCursorResultSets(statement);
  }

  // 本方法用於執行insert、delete、update等類型的SQL語句,並且會根據配置的
  // KeyGenerator獲取數據庫生成的主鍵
  @Override
  public int update(Statement statement) throws SQLException {
    // 獲取SQL語句 及parameterObject
    String sql = boundSql.getSql();
    Object parameterObject = boundSql.getParameterObject();
    // 獲取配置的KeyGenerator 數據庫主鍵生成器
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    int rows;
    if (keyGenerator instanceof Jdbc3KeyGenerator) {
      // 執行SQL語句
      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
      // 獲取更新的條數
      rows = statement.getUpdateCount();
      // 將數據庫生成的主鍵添加到parameterObject中
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else if (keyGenerator instanceof SelectKeyGenerator) {
      // 執行SQL語句
      statement.execute(sql);
      // 獲取更新的條數
      rows = statement.getUpdateCount();
      // 執行<selectKey>節點中配置的SQL語句,將從數據庫獲取到的主鍵 添加到parameterObject中
      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
    } else {
      statement.execute(sql);
      rows = statement.getUpdateCount();
    }
    return rows;
  }

  @Override
  public void parameterize(Statement statement) {
    // N/A
  }

}

PreparedStatementHandler

PreparedStatementHandler底層依賴於java.sql.PreparedStatement來完成數據庫的相關操作。其中的parameterize()方法中,會調用前面介紹的ParameterHandler的setParameters()方法 完成 SQL語句的參數綁定。instantiateStatement()方法直接調用JDBC Connection的prepareStatement()方法創建PreparedStatement對象。

public class PreparedStatementHandler extends BaseStatementHandler {

  // 構造方法主要用於屬性的初始化
  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    // 獲取SQL語句
    String sql = boundSql.getSql();
    // 根據mappedStatement持有的KeyGenerator的類型進行不同的處理
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      // 獲取主鍵列
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        // 返回數據庫生成的主鍵
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        // 在insert語句執行完後,會將keyColumnNames指定的列返回
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      // 如果結果集類型是DEFAULT默認的,則直接通過connection獲取PreparedStatement對象
      return connection.prepareStatement(sql);
    } else {
      // 否則,設置結果集類型,設置結果集爲只讀
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

  // 因爲是PrepareStatement對象,所以需要處理佔位符"?"
  // 使用了前面介紹的ParameterHandler組件完成
  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

  // 下面的這些方法,除了多了一步 將Statement對象強轉成PreparedStatement對象
  // 其它的幾乎與SimpleStatementHandler一樣
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleResultSets(ps);
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.addBatch();
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.handleCursorResultSets(ps);
  }

  @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;
  }

}

另外,StatementHandler接口還有一個CallableStatementHandler的實現。其底層依賴於java.sql.CallableStatement調用指定的存儲過程,其parameterize()方法也會調用ParameterHandler的setParameters()方法完成SQL語句的參數綁定,並指定輸出參數的索引位置和JDBC類型。其餘方法與前面介紹的ResultSetHandler實現類似,唯一區別是會調用ResultSetHandler的handleOutputParameters()方法 處理輸出參數。

看到這裏,我們可以發現StatementHandler組件依賴ParameterHandler組件 和 ResultSetHandler組件 完成了MyBatis的核心功能,它控制着參數綁定、SQL語句執行、結果集映射等一系列核心流程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章