mybatis 源碼分析(六)StatementHandler 主體結構分析

  • 2019 年 10 月 3 日
  • 筆記

分析到這裡的時候,mybatis 初始化、介面、事務、快取等主要功能都已經講完了,現在就還剩下 StatementHandler 這個真正幹活的傢伙沒有分析了;所以接下來的部落格內容主要和資料庫的關係比較密切,而 StatementHandler 的主要流程也基本是和 JDBC 的流程是一一對應的;

一、StatementHandler 執行流程

在 mybatis 系列文章的第一篇,我放了一張 mybatis 整體的執行流程圖:

從上面的圖中也能比較清楚的看到 StatementHandler 的職責:獲取 Statement -> 設置參數 -> 查詢資料庫 -> 將查詢結果映射為 JavaBean,從這裡也能看到是和我們使用原生 JDBC 的流程是一樣的;而整個過程 StatementHandler 又將其拆分成了部分:

  • KeyGenerator:主鍵設置
  • ParameterHandler:參數設置
  • ResultSetHandler:結果集設置

這裡我們首先介紹 StatementHandler 的類結構:

  • RoutingStatementHandler:路由處理器,這個相當於一個靜態代理,根據 MappedStatement.statementType 創建對應的對處理器;
  • SimpleStatementHandler:不需要預編譯的簡單處理器;
  • PreparedStatementHandler:預編譯的 SQL 處理器;
  • CallableStatementHandler:主要用於存儲過程的調度;

這裡的 SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler 同時也和 JDBC 的三個 Statement 一一對應;首先我們還是先看一下介面方法:

public interface StatementHandler {    Statement prepare(Connection connection) throws SQLException; // 獲取 Statement    void parameterize(Statement statement) throws SQLException;   // 參數化    void batch(Statement statement) throws SQLException;          // 批處理    int update(Statement statement) throws SQLException;    <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException;    BoundSql getBoundSql();                 // 獲取綁定sql    ParameterHandler getParameterHandler(); // 得到參數處理器  }

其中公共的方法都封裝到了 BaseStatementHandler 中(這裡使用的是模版模式);

// 獲取 Statement  @Override  public Statement prepare(Connection connection) throws SQLException {    ErrorContext.instance().sql(boundSql.getSql());    Statement statement = null;    try {      statement = instantiateStatement(connection); // 實例化 Statement      setStatementTimeout(statement);               // 設置超時      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;

二、StatementHandler 子類

1. RoutingStatementHandler

靜態代理模式,根據 MappedStatement.statementType 創建對應的對處理,默認是 PREPARED,可以使用 XML 配置或者註解的方式指定;

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);    // 插件攔截    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);    return statementHandler;  }
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {    switch (ms.getStatementType()) {  // 根據語句類型,委派到不同的語句處理器(STATEMENT|PREPARED|CALLABLE)      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());    }  }

2. SimpleStatementHandler

其功能和 JDBC.Statement 對應,從初始化方法也可以看到使用的是簡單 Statement;

@Override  protected Statement instantiateStatement(Connection connection) throws SQLException {    // 調用 Connection.createStatement    if (mappedStatement.getResultSetType() != null) {      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);    } else {      return connection.createStatement();    }  }
@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) {      statement.execute(sql, Statement.RETURN_GENERATED_KEYS);      rows = statement.getUpdateCount();      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);    } else if (keyGenerator instanceof SelectKeyGenerator) {      statement.execute(sql);      rows = statement.getUpdateCount();      keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);    } else {      //如果沒有keyGenerator,直接調用Statement.execute和Statement.getUpdateCount      statement.execute(sql);      rows = statement.getUpdateCount();    }    return rows;  }

這裡在更新的時候需要區分 KeyGenerator,因為使用的是簡單 Statement,所以需要在查詢的時候指定返回主鍵 statement.execute(sql, Statement.RETURN_GENERATED_KEYS);

3. PreparedStatementHandler

其功能和 PreparedStatement 對應,這裡初始化的時候可以看到比 SimpleStatementHandler 要複雜一些,因為 PreparedStatement 更新返回主鍵有三個方法,詳細分析後面會單獨放一篇詳細講解;

@Override  protected Statement instantiateStatement(Connection connection) throws SQLException {    //調用Connection.prepareStatement    String sql = boundSql.getSql();    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {      String[] keyColumnNames = mappedStatement.getKeyColumns();      if (keyColumnNames == null) {        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);      } else {        return connection.prepareStatement(sql, keyColumnNames);      }    } else if (mappedStatement.getResultSetType() != null) {      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);    } else {      return connection.prepareStatement(sql);    }  }

還有一點不同的就是 PreparedStatement 需要參數化設置,就是設置預編譯 SQL 對應佔位符的參數;

// DefaultParameterHandler  @Override  public void setParameters(PreparedStatement ps) throws SQLException {    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) {          // 如果不是OUT,才設進去          Object value;          String propertyName = parameterMapping.getProperty();          if (boundSql.hasAdditionalParameter(propertyName)) {            // 若有額外的參數, 設為額外的參數            value = boundSql.getAdditionalParameter(propertyName);          } else if (parameterObject == null) {            // 若參數為null,直接設null            value = null;          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {            // 若參數有相應的TypeHandler,直接設object            value = parameterObject;          } else {            // 除此以外,MetaObject.getValue反射取得值設進去            MetaObject metaObject = configuration.newMetaObject(parameterObject);            value = metaObject.getValue(propertyName);          }          TypeHandler typeHandler = parameterMapping.getTypeHandler();          JdbcType jdbcType = parameterMapping.getJdbcType();          if (value == null && jdbcType == null) {            // 不同類型的set方法不同,所以委派給子類的setParameter方法            jdbcType = configuration.getJdbcTypeForNull();          }          typeHandler.setParameter(ps, i + 1, value, jdbcType);        }      }    }  }

另外還有 CallableStatementHandler ,程式碼也很簡單,這裡就不詳細分析了;