Mybatis3.3.x技術內幕(六):StatementHandler(Box stop here)

神通廣大的猴哥SqlSession,把雜事委託給二弟Executor來處理,二弟Executor可不那麼傻,因而它又把事情委託給三弟StatementHandler,三弟憨厚老實,本着Box stop here的精神,無怨無悔不說,還不辭辛苦,因而,一代偉人就此誕生了。
java

三弟StatementHandler從跑龍套開始,逐漸崛起,前後擔任武術指導、製片、監製等職位,最後,經驗豐富的它當上了導演,拍了屬於本身的做品:三弟電影,又稱3D電影。sql

有關Box stop here的故事,請自行了解。數據庫



1. 數據庫操做invoke時序圖

(Made In Visual Paradigm)apache

本文重點分析StatementHandler和ParameterHandler是如何與Executor共襄盛舉的。(上圖中的execute()失誤畫錯了,應該是executeQuery()設計模式

2. Executor內使用StatementHandler模板

Statement stmt;
StatementHandler handler;
// 判斷緩存內是否存在stmt
if (...) {
   // 不存在,就建立一個Statement(多是Statement、PrepareStatement、CallableStatement)
    stmt = handler.prepare(connection);
}
handler.parameterize(stmt);

不管是何種Executor實現類,都使用上面的模板方法調用,因此,StatementHandler隱藏了建立Statement對象和parameterize初始化參數的祕密。
緩存


3. StatementHandler接口設計與類結構圖

public interface StatementHandler {
  Statement prepare(Connection connection)
      throws SQLException;
  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();
  ParameterHandler getParameterHandler();
}

類結構圖。
網絡

(Made In Intellij Idea IDE)app

SimpleStatementHandler:用於處理Statement對象的數據庫操做。ide

PreparedStatementHandler:用於處理PreparedStatement對象的數據庫操做。ui

CallableStatementHandler:用於處理CallableStatement對象的數據庫操做。(存儲過程)


RoutingStatementHandler:用於建立上面三種Handler的策略類。(簡直是裝飾設計模式的極大錯誤示範,別覺得長的帥,我就不抨擊它,根本沒有存在的理由


4. 基類BaseStatementHandler源碼解析

protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

對於BaseStatementHandler,完成了Sql相關信息的保存工做,也就是把通用食材準備好了。咱們重點關注上面的抽象方法便可,也就是建立什麼樣的Statement對象,具體由子類去實現,子類至關於廚師,面對相同的食材,廚師對其烹飪的手法略有不一樣。


4.1. SimpleStatementHandler源碼解析

   @Override
  public void batch(Statement statement) throws SQLException {
    String sql = boundSql.getSql();
    statement.addBatch(sql);
  }
  
  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    return resultSetHandler.<E>handleResultSets(statement);
  }
  
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    if (mappedStatement.getResultSetType() != null) {
      return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.createStatement();
    }
  }

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

建立了一個Statement對象,因爲Statement對象不支持「?」參數,因此,parameterize()是空實現。


4.2. PreparedStatementHandler源碼解析

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

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

建立了一個PrepareStatement對象,parameterize()則委託給ParameterHandler去設置。


4.3. CallableStatementHandler源碼解析

  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getResultSetType() != null) {
      return connection.prepareCall(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareCall(sql);
    }
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    registerOutputParameters((CallableStatement) statement);
    parameterHandler.setParameters((CallableStatement) statement);
  }

建立了一個CallableStatement對象,parameterize()則委託給ParameterHandler去設置。


5. DefaultParameterHandler源碼解析

對於ParameterHandler,它只有一個惟一的實現類:DefaultParameterHandler.setParameter()。

  @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);
            // ...
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

請看方法參數,參數要求是一個PreparedStatement對象,固然能夠處理PreparedStatement。然而,爲什麼它還能夠處理CallableStatement對象呢?

緣由在這裏,請看JDK中有關java.sql.CallableStatement的接口描述。

IN parameter values are set using the set methods inherited from  PreparedStatement

對於存儲過程的IN參數來講,CallableStatement繼承自PreparedStatement,傳遞進來的CallableStatement對象,其實也是PreparedStatement對象


6. 抨擊RoutingStatementHandler的錯誤設計示範

public class RoutingStatementHandler implements StatementHandler {

  private final StatementHandler delegate;

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

  }

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

衆所周知,裝飾設計模式,是爲了擴展對象功能,避免無限繼承,而存在的一種設計模式。Mybatis倒好,竟然把裝飾設計模式當策略類來使用,又不作任何功能擴展,簡直就是很是錯誤的設計示範。徹底可使用一個普通的策略類來完成,何須搞一個裝飾設計模式?

幸虧我不是羅果先生,不然說的話,比這個要難聽十倍。


7. StatementHandler的建立時機和建立策略控制

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  }

Executor每執行一個query或update動做,都會建立一個StatementHandler對象。

StatementHandler建立策略有三種。(默認爲PREPARED)

public enum StatementType {
  STATEMENT, PREPARED, CALLABLE
}

建立策略到底如何控制?能夠在Mapper.xml內配置statementType屬性。

<select id="findAllStudents" resultMap="StudentResult" statementType="STATEMENT">
	SELECT * FROM STUDENTS
</select>

獲取StatementType的源碼以下。

org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode()。

StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));

默認值StatementType.PREPARED。


至此,一個Sql命令,通過馬拉松式的長跑(SqlSession-->Executor-->StatementHandler-->Statement-->DB),終於如願以償的到達了終點。



8. 看不懂本篇博文內容的自救良藥

做爲博文做者,不少時候,很想向讀者描述清楚一個東西究竟是什麼,可是,因爲讀者用功程度和瀏覽習慣不一樣,不少人依然看不明白寫那麼多到底闡述了些什麼。不要緊,咱有自救良藥。

8.1 忘記SimpleStatementHandler,迴歸本真

SimpleStatementHandler等於下面兩句話。

Statement stm = conn.createStatement()
return stm.execute(sql);


8.2 忘記PreparedStatementHandler,迴歸本真

PreparedStatementHandler等於下面三句話。

PreparedStatement pstm = conn.prepareStatement(sql);
pstm.setString(1, "Hello");
return pstm.execute();


8.3 忘記CallableStatementHandler,迴歸本真

CallableStatementHandler等於下面六句話。

CallableStatement cs = conn.prepareCall("{call pr_add(?,?,?)}");
cs.registerOutParameter(3, Types.INTEGER);
cs.setInt(1, 10);
cs.setString(2, "Hello");
cs.execute();
return cs.getInt(3);


版權提示:文章出自開源中國社區,若對文章感興趣,可關注個人開源中國社區博客(http://my.oschina.net/zudajun)。(通過網絡爬蟲或轉載的文章,常常丟失流程圖、時序圖,格式錯亂等,仍是看原版的比較好)

相關文章
相關標籤/搜索