1. 程式人生 > >mybatis 原始碼分析(六)StatementHandler 主體結構分析

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

分析到這裡的時候,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 ,程式碼也很簡單,這裡就不詳細分析了