Mybatis源碼解析04-關鍵組件Executor

前面介紹了

接下來看一下Executor,Executor是Mybatis的執行器,接口提供的主要功能有:

  1. 更新、查詢、提交、回滾
  2. 緩存處理(一級、二級緩存 ),如創建緩存key、清理緩存等

常用的與數據庫交互的核心方法有:

public interface Executor {
     
  int update(MappedStatement ms, Object parameter) throws SQLException;
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;  
  <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;  
  void commit(boolean required) throws SQLException;  
  void rollback(boolean required) throws SQLException;  
}

可以看到Executor接口有多個實現類,整體類關係如下圖所示:

image.png

接下來一個一個看這幾個實現類

BaseExecutor

BaseExecutor是一個抽象類,是[SimpleExecutor BatchExecutor ReuseExecutor ClosedExecutor]這幾個Executor的父類,從該抽象類的定義可以看出來,有以下幾個抽象方法需要子類去實現

protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;  
protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;  
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)  
    throws SQLException;  
protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)  
    throws SQLException;

而查看這幾個抽象方法被調用的地方可以知道是利用了模板方法模式(Template Method Pattern),如doUpdate方法被調用的地方:

@Override  
public int update(MappedStatement ms, Object parameter) throws SQLException {
     
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());  
  if (closed) {
     
    throw new ExecutorException("Executor was closed.");  
  }  
  clearLocalCache();  
  return doUpdate(ms, parameter);  
}

除了基於模板方法實現的update、query、flushStatements,還提供了一些其他的功能。

SimpleExecutor

SimpleExecutor實現了BaseExecutor的三個抽象方法,沒有其他特殊的邏輯,三個override方法的基本邏輯都是

  1. 從MappedStatement獲取Mybatis的全局配置模型Configuration
  2. 調用Configuration的newStatementHandler方法創建StatementHandler
  3. 根據StatementHandler與Connedtion來構建好Statement
  4. 調用StatementHandler對應的方法
BatchExecutor

從名字可知實現了有關批量操作的邏輯,doQuery、doQueryCursor與SimpleExecutor的方法基本相同。從源碼可以看到,BatchExecutor多了幾個內部屬性,這裏重點看一下doUpdate方法

public class BatchExecutor extends BaseExecutor {
     
  // 批量更新返回值, 0x80000000 + 1002
  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;  

  // statement的集合
  private final List<Statement> statementList = new ArrayList<>();  
  // 批量執行結果集合
  private final List<BatchResult> batchResultList = new ArrayList<>();  

  // 當前sql(其實應該叫上一次比較合適)
  private String currentSql;  
  // 當前Statement(其實應該叫上一次比較合適)
  private MappedStatement currentStatement;
  
  @Override  
  public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
     
    // 獲取Configuration
    final Configuration configuration = ms.getConfiguration();  
    // 新創建StatementHandler
    final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);  
    // 拿到DoundSql
    final BoundSql boundSql = handler.getBoundSql();  
    // 拿到sql
    final String sql = boundSql.getSql();  
    final Statement stmt;  
    // 如果要執行的和上一次
    if (sql.equals(currentSql) && ms.equals(currentStatement)) {
     
	  // list中的最後一個
      int last = statementList.size() - 1;  
      stmt = statementList.get(last);  
      // 事務超時時間
      applyTransactionTimeout(stmt);  
      // 確定statement的參數
      handler.parameterize(stmt);// fix Issues 322 
      // 從list中獲取batchResult
      BatchResult batchResult = batchResultList.get(last);  
      // batchResult加入參數對象
      batchResult.addParameterObject(parameterObject);  
    } else {
     
      // 不一樣則一步一步處理
      Connection connection = getConnection(ms.getStatementLog());  
      stmt = handler.prepare(connection, transaction.getTimeout());  
      handler.parameterize(stmt);    // fix Issues 322 
      // 設置currentSql和statement爲這次調用方法進來的statement
      currentSql = sql;  
      currentStatement = ms;  
      statementList.add(stmt);  
      batchResultList.add(new BatchResult(ms, sql, parameterObject));  
    }  
    調用StatementHandler的batch方法
    handler.batch(stmt);  
    return BATCH_UPDATE_RETURN_VALUE;  
  }
  
}

需要注意的是BatchExecutor針對的是同一個session。

ReuseExecutor

resuse的重用主要體現在其內部有個存放Statement的Map,其KEY是從BoundSql拿到的sql,可以看一下ResuseExecutor的prepareStatement方法:

public class ReuseExecutor extends BaseExecutor {
     
    // 存放statement的map,以實現重用的目的
	private final Map<String, Statement> statementMap = new HashMap<>();
	private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
     
	  Statement stmt;  
	  BoundSql boundSql = handler.getBoundSql();  
	  String sql = boundSql.getSql();  
	  // 如果在statementMap中有,則從map中獲取
	  if (hasStatementFor(sql)) {
     
	    stmt = getStatement(sql);  
	    applyTransactionTimeout(stmt);  
	  } else {
     
	    Connection connection = getConnection(statementLog);  
	    stmt = handler.prepare(connection, transaction.getTimeout());  
	    putStatement(sql, stmt);  
	  }  
	  handler.parameterize(stmt);  
	  return stmt;  
	}  
	  
	private boolean hasStatementFor(String sql) {
     
	  try {
     
	    Statement statement = statementMap.get(sql);  
	    return statement != null && !statement.getConnection().isClosed();  
	  } catch (SQLException e) {
     
	    return false;  
	}
	private Statement getStatement(String s) {
     
	  return statementMap.get(s);  
	}  
	  
	private void putStatement(String sql, Statement stmt) {
     
	  statementMap.put(sql, stmt);  
	}
}
CachingExecutor

從名字可以看出來是和緩存相關的,從其構造方法可以看到用到了裝飾者模式,也就是說CachingExecutor是某個Executor實例的裝飾器,

public class CachingExecutor implements Executor {
   

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
   
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }  

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
   
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 構造CacheKey
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
   
    // 從MappedStatement獲取Cache實例
    Cache cache = ms.getCache();
    if (cache != null) {
   
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
   
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        // 從緩存獲取
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
   
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }
}

可以看到,如果有配置二級緩存,即從MappedStatement拿到的二級緩存實例不爲空,則根據CacheKey從緩存拿,拿不到則執行被裝飾的Executor的query方法。

ClosedExecutor

做爲ResultLoaderMap的私有內部類,可以看源碼是這樣描述的

We are using a new executor because we may be (and likely are) on a new thread
and executors aren’t thread safe. (Is this sufficient?)

即:

我們正在使用一個新的執行器,因爲我們可能(並且很可能)在一個新線程上
執行者不是線程安全的。

最後再回顧一下,Mybatis 源碼解析 - 配置、啓動加載裏面有關於默認Executor類型的配置,如下所示:

<!-- 配置默認的執行器。SIMPLE 就是普通的執行器;REUSE 執行器會重用預處理語句(PreparedStatement); BATCH 執行器不僅重用語句還會執行批量更新。 -->
        <setting name="defaultExecutorType" value="SIMPLE"/>

參考

Mybatis解析-執行器Executor詳解

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