天天看點

mybatis随筆五之Executor

在上一篇文章我們分析到了mapper接口方法的實作實際上是交由代理類來實作的,并最終調用Executor來查詢,接下來我們對
executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)這個方法進行分析。      
@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }      
ms.getBoundSql内部調用RawSqlSource的getBoundSql方法,該方法又調用了StaticSqlSource的getBoundSql方法,并在該方法内部初始化了一個BoundSql對象,如下是BoundSql的參數      
private String sql;   //需要執行的sql語句
  private List<ParameterMapping> parameterMappings;   //參數與資料庫列的對應關系
  private Object parameterObject;                     //查詢傳遞的參數
  private Map<String, Object> additionalParameters;
  private MetaObject metaParameters;      
createCacheKey是調用的BaseExecutor方法根據mappedStatement的id,rowBounds的offset、limit值、要執行的sql語句、傳遞的參數、environment的id來建立cacheKey。
在query的時候檢視是否有cache,如果有則使用cache結果,否則使用内部delegate的query方法,這裡跳轉到了BaseExecutor的query方法,該方法内部又使用了queryFromDatabase方法。      
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }      
方法比較簡單将查詢結果儲存在chche中,調用doQuery方法,BaseExecutor的doQuery方法是個抽象方法,是以這裡實際使用的是子類SimpleExecutor的doQuery方法
      
 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }      
我們先看看newStatementHandler這個方法,這個方法主要做了兩件事情,執行個體化了一個RoutingStatementHandler對象,将攔截目标是statementHandler的攔截器構成攔截鍊。      
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;
  }      
RoutingStatementHandler采用的是裝飾設計模式,内部delegate委托的是PreparedStatementHandler對象,是以它的構造方法内部去建立了一個PreparedStatementHandler對象      
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
      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);
  }      
建構的過程中也初始化了parameterHandler、resultSetHandler兩個對象,顧名思義一個是用來處理參數的一個是用來處理結果的。
是以在建立StatementHandler的同時其餘兩個handler也被建立出來了。
接下來使用prepareStatement來建構參數。      
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection);
    handler.parameterize(stmt);
    return stmt;
  }      
在這個方法内部,我們最關注的是倒數二、三句。
handler.prepare類似mapper交由statementHandler的代理對象來執行,若沒有針對其的攔截方法則還是調用RoutingStatementHandler的prepare方法。      
public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      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);
    }
  }      
這裡主要做了幾件事:傳回preparedStatement對象、設定查詢逾時時間、設定每次批量傳回的結果行數。
handler.parameterize(stmt)方法類似也是交由statementHandler的代理對象來執行,最終也使用RoutingStatementHandler的parameterize方法。      
public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }      
在這個方法裡主要是根據定義的入參的javaType、jdbcType類型來選擇合适的typeHandler來設定參數,因為我們使用的是long類型,是以typeHandler使用的是LongTypeHandler。
這樣我們就把preparedStatement所需的參數全部填充了,最終進入handler.<E>query(stmt, resultHandler)方法。
query方法也會先調用攔截鍊的方法,最後使用RoutingStatementHandler的query方法。      
@Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);
  }