天天看點

Mybatis的Executor介紹(一) 5       Mybatis的Executor介紹(一)

目錄

<a href="#_Toc471399571">5       Mybatis的Executor介紹(一)</a>

<a href="#_Toc471399572">5.1        SimpleExecutor</a>

<a href="#_Toc471399573">5.2        ReuseExecutor</a>

<a href="#_Toc471399574">5.3        BatchExecutor</a>

<a href="#_Toc471399575">5.4        Executor的選擇</a>

<a href="#_Toc471399576">5.4.1         預設的Executor</a>

       Mybatis中所有的Mapper語句的執行都是通過Executor進行的,Executor是Mybatis的一個核心接口,其定義如下。從其定義的接口方法我們可以看出,對應的增删改語句是通過Executor接口的update方法進行的,查詢是通過query方法進行的。雖然Executor接口的實作類有BaseExecutor和CachingExecutor,而BaseExecutor的子類又有SimpleExecutor、ReuseExecutor和BatchExecutor,但BaseExecutor是一個抽象類,其隻實作了一些公共的封裝,而把真正的核心實作都通過方法抽象出來給子類實作,如doUpdate()、doQuery();CachingExecutor隻是在Executor的基礎上加入了緩存的功能,底層還是通過Executor調用的,是以真正有作用的Executor隻有SimpleExecutor、ReuseExecutor和BatchExecutor。它們都是自己實作的Executor核心功能,沒有借助任何其它的Executor實作,它們是實作不同也就注定了它們的功能也是不一樣的。Executor是跟SqlSession綁定在一起的,每一個SqlSession都擁有一個新的Executor對象,由Configuration建立。

public interface Executor {

  ResultHandler NO_RESULT_HANDLER = null;

  int update(MappedStatement ms, Object parameter) throws SQLException;

  &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

  &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

  List&lt;BatchResult&gt; flushStatements() throws SQLException;

  void commit(booleanrequired) throws SQLException;

  void rollback(booleanrequired) throws SQLException;

  CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

  boolean isCached(MappedStatement ms, CacheKey key);

  void clearLocalCache();

  void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class&lt;?&gt; targetType);

  Transaction getTransaction();

  void close(booleanforceRollback);

  boolean isClosed();

  void setExecutorWrapper(Executor executor);

}

       下面是BaseExecutor的源碼,我們可以看看它是怎麼實作Executor接口的,是怎麼開放接口給子類實作的。

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  protected Transaction transaction;

  protected Executor wrapper;

  protected ConcurrentLinkedQueue&lt;DeferredLoad&gt; deferredLoads;

  protected PerpetualCache localCache;

  protected PerpetualCache localOutputParameterCache;

  protected Configuration configuration;

  protected int queryStack = 0;

  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {

    this.transaction = transaction;

    this.deferredLoads = new ConcurrentLinkedQueue&lt;DeferredLoad&gt;();

    this.localCache = new PerpetualCache("LocalCache");

    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");

    this.closed = false;

    this.configuration = configuration;

    this.wrapper = this;

  }

  @Override

  public Transaction getTransaction() {

    if (closed) {

      throw new ExecutorException("Executor was closed.");

    }

    returntransaction;

  public void close(boolean forceRollback) {

    try {

      try {

        rollback(forceRollback);

      } finally {

        if (transaction != null) {

          transaction.close();

        }

      }

    } catch (SQLException e) {

      // Ignore.  There's nothing that can be done at this point.

      log.warn("Unexpected exception on closing transaction.  Cause: " + e);

    } finally {

      transaction = null;

      deferredLoads = null;

      localCache = null;

      localOutputParameterCache = null;

      closed = true;

  public boolean isClosed() {

    return closed;

  public int update(MappedStatement ms, Object parameter) throws SQLException {

    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());

    clearLocalCache();

    return doUpdate(ms, parameter);

  public List&lt;BatchResult&gt; flushStatements() throws SQLException {

    return flushStatements(false);

  public List&lt;BatchResult&gt; flushStatements(boolean isRollBack) throws SQLException {

    return doFlushStatements(isRollBack);

  public &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

    BoundSql boundSql = ms.getBoundSql(parameter);

    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);

 }

  @SuppressWarnings("unchecked")

  public &lt;E&gt; List&lt;E&gt; query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());

    if (queryStack == 0 &amp;&amp; ms.isFlushCacheRequired()) {

      clearLocalCache();

    List&lt;E&gt; list;

      queryStack++;

      list = resultHandler == null ? (List&lt;E&gt;) localCache.getObject(key) : null;

      if (list != null) {

        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);

      } else {

        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);

      queryStack--;

    if (queryStack == 0) {

      for (DeferredLoad deferredLoad : deferredLoads) {

        deferredLoad.load();

      // issue #601

      deferredLoads.clear();

      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {

        // issue #482

        clearLocalCache();

    return list;

  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class&lt;?&gt; targetType) {

    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);

    if (deferredLoad.canLoad()) {

      deferredLoad.load();

    } else {

      deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));

  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {

    CacheKey cacheKey = new CacheKey();

    cacheKey.update(ms.getId());

    cacheKey.update(Integer.valueOf(rowBounds.getOffset()));

    cacheKey.update(Integer.valueOf(rowBounds.getLimit()));

    cacheKey.update(boundSql.getSql());

    List&lt;ParameterMapping&gt; parameterMappings = boundSql.getParameterMappings();

    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

    // mimic DefaultParameterHandler logic

    for (int i = 0; i &lt; parameterMappings.size(); i++) {

      ParameterMapping parameterMapping = parameterMappings.get(i);

      if (parameterMapping.getMode() != ParameterMode.OUT) {

        Object value;

        String propertyName = parameterMapping.getProperty();

        if (boundSql.hasAdditionalParameter(propertyName)) {

          value = boundSql.getAdditionalParameter(propertyName);

        } else if (parameterObject == null) {

          value = null;

        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {

          value = parameterObject;

        } else {

          MetaObject metaObject = configuration.newMetaObject(parameterObject);

          value = metaObject.getValue(propertyName);

        cacheKey.update(value);

    if (configuration.getEnvironment() != null) {

      // issue #176

      cacheKey.update(configuration.getEnvironment().getId());

    return cacheKey;

  }   

  public boolean isCached(MappedStatement ms, CacheKey key) {

    return localCache.getObject(key) != null;

  public void commit(boolean required) throws SQLException {

      throw new ExecutorException("Cannot commit, transaction is already closed");

    flushStatements();

    if (required) {

      transaction.commit();

  public void rollback(boolean required) throws SQLException {

    if (!closed) {

        flushStatements(true);

        if (required) {

          transaction.rollback();

  public void clearLocalCache() {

      localCache.clear();

      localOutputParameterCache.clear();

  protected abstract int doUpdate(MappedStatement ms, Object parameter)

      throws SQLException;

  protected abstract List&lt;BatchResult&gt; doFlushStatements(boolean isRollback)

  protected abstract &lt;E&gt; List&lt;E&gt; doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)

  protected void closeStatement(Statement statement) {

    if (statement != null) {

        statement.close();

      } catch (SQLException e) {

        // ignore

  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {

    if (ms.getStatementType() == StatementType.CALLABLE) {

      final Object cachedParameter = localOutputParameterCache.getObject(key);

      if (cachedParameter != null &amp;&amp; parameter != null) {

        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);

        final MetaObject metaParameter = configuration.newMetaObject(parameter);

        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {

          if (parameterMapping.getMode() != ParameterMode.IN) {

            final String parameterName = parameterMapping.getProperty();

            final Object cachedValue = metaCachedParameter.getValue(parameterName);

            metaParameter.setValue(parameterName, cachedValue);

          }

  private &lt;E&gt; List&lt;E&gt; queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

    localCache.putObject(key, EXECUTION_PLACEHOLDER);

      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);

      localCache.removeObject(key);

    localCache.putObject(key, list);

      localOutputParameterCache.putObject(key, parameter);

    returnlist;

  protected Connection getConnection(Log statementLog) throws SQLException {

    Connection connection = transaction.getConnection();

    if (statementLog.isDebugEnabled()) {

      return ConnectionLogger.newInstance(connection, statementLog, queryStack);

      return connection;

  public void setExecutorWrapper(Executor wrapper) {

    this.wrapper = wrapper;

  private static class DeferredLoad {

    private final MetaObject resultObject;

    private final String property;

    private final Class&lt;?&gt; targetType;

    private final CacheKey key;

    private final PerpetualCache localCache;

    private final ObjectFactory objectFactory;

    private final ResultExtractor resultExtractor;

    // issue #781

    public DeferredLoad(MetaObject resultObject,

                        String property,

                        CacheKey key,

                        PerpetualCache localCache,

                        Configuration configuration,

                        Class&lt;?&gt; targetType) {

      this.resultObject = resultObject;

      this.property = property;

      this.key = key;

      this.localCache = localCache;

      this.objectFactory = configuration.getObjectFactory();

      this.resultExtractor = new ResultExtractor(configuration, objectFactory);

      this.targetType = targetType;

    public boolean canLoad() {

      return localCache.getObject(key) != null &amp;&amp; localCache.getObject(key) != EXECUTION_PLACEHOLDER;

    public void load() {

      @SuppressWarnings( "unchecked" )

      // we suppose we get back a List

      List&lt;Object&gt; list = (List&lt;Object&gt;) localCache.getObject(key);

      Object value = resultExtractor.extractObjectFromList(list, targetType);

      resultObject.setValue(property, value);

       SimpleExecutor是Mybatis執行Mapper語句時預設使用的Executor。它提供最基本的Mapper語句執行功能,沒有過多的封裝的。SimpleExecutor的源碼如下。

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {

    super(configuration, transaction);

  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {

    Statement stmt = null;

      Configuration configuration = ms.getConfiguration();

      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);

      stmt = prepareStatement(handler, ms.getStatementLog());

      returnhandler.update(stmt);

      closeStatement(stmt);

  public &lt;E&gt; List&lt;E&gt; doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {

      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

      return handler.&lt;E&gt;query(stmt, resultHandler);

  public List&lt;BatchResult&gt; doFlushStatements(booleanisRollback) throws SQLException {

    return Collections.emptyList();

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {

    Statement stmt;

    Connection connection = getConnection(statementLog);

    stmt = handler.prepare(connection);

    handler.parameterize(stmt);

    returnstmt;

       ReuseExecutor,顧名思義,是可以重用的Executor。它重用的是Statement對象,它會在内部利用一個Map把建立的Statement都緩存起來,每次在執行一條SQL語句時,它都會去判斷之前是否存在基于該SQL緩存的Statement對象,存在而且之前緩存的Statement對象對應的Connection還沒有關閉的時候就繼續用之前的Statement對象,否則将建立一個新的Statement對象,并将其緩存起來。因為每一個新的SqlSession都有一個新的Executor對象,是以我們緩存在ReuseExecutor上的Statement的作用域是同一個SqlSession。以下是ReuseExecutor的源碼。

public class ReuseExecutor extends BaseExecutor {

  private final Map&lt;String, Statement&gt; statementMap = new HashMap&lt;String, Statement&gt;();

  public ReuseExecutor(Configuration configuration, Transaction transaction) {

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

    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

    return handler.&lt;E&gt;query(stmt, resultHandler);

    for (Statement stmt : statementMap.values()) {

    statementMap.clear();

    BoundSql boundSql = handler.getBoundSql();

    String sql = boundSql.getSql();

    if (hasStatementFor(sql)) {

      stmt = getStatement(sql);

      Connection connection = getConnection(statementLog);

      stmt = handler.prepare(connection);

      putStatement(sql, stmt);

    return stmt;

  private boolean hasStatementFor(String sql) {

      return statementMap.keySet().contains(sql) &amp;&amp; !statementMap.get(sql).getConnection().isClosed();

      return false;

  private Statement getStatement(String s) {

    return statementMap.get(s);

  private void putStatement(String sql, Statement stmt) {

    statementMap.put(sql, stmt);

       BatchExecutor的設計主要是用于做批量更新操作的。其底層會調用Statement的executeBatch()方法實作批量操作。以下是BatchExecutor的源碼。

       既然BaseExecutor下面有SimpleExecutor、ReuseExecutor和BatchExecutor,Executor還有一個CachingExecutor的實作,那我們怎麼選擇使用哪個Executor呢?預設情況下Mybatis的全局配置cachingEnabled=”true”,這就意味着預設情況下我們就會使用一個CachingExecutor來包裝我們真正使用的Executor,這個在後續介紹Mybatis的緩存的文章中會介紹。那我們真正使用的BaseExecutor是怎麼确定的呢?是通過我們在建立SqlSession的時候确定的。SqlSession都是通過SqlSessionFactory的openSession()建立的,SqlSessionFactory提供了一系列的openSession()方法。

public interface SqlSessionFactory {

  SqlSession openSession();

  SqlSession openSession(boolean autoCommit);

  SqlSession openSession(Connection connection);

  SqlSession openSession(TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType);

  SqlSession openSession(ExecutorType execType, boolean autoCommit);

  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);

  SqlSession openSession(ExecutorType execType, Connection connection);

  Configuration getConfiguration();

       從上面SqlSessionFactory提供的方法來看,它一共提供了兩類建立SqlSession的方法,一類是沒有指定ExecutorType的,一類是指定了ExecutorType的。很顯然,指定了ExecutorType時将使用ExecutorType對應類型的Executor。ExecutorType是一個枚舉類型,有SIMPLE、REUSE和BATCH三個對象。

       而沒有指定ExecutorType時将使用預設的Executor。Mybatis預設的Executor是SimpleExecutor,我們可以通過Mybatis的全局配置defaultExecutorType來進行配置,其可選值也是SIMPLE、REUSE和BATCH。

      &lt;setting name="defaultExecutorType" value="SIMPLE"/&gt;

       注意,當Mybatis整合Spring後,Spring掃描後生成的Mapper對象,底層使用的SqlSession都是用的預設的Executor。如果我們需要在程式中使用非預設的Executor時,我們可以在Spring的bean容器中聲明SqlSessionFactoryBean,然後在需要指定Executor的類中注入SqlSessionFactory,通過SqlSessionFactory來建立指定ExecutorType的SqlSession。

參考文檔

       Mybatis源碼

(注:本文是基于Mybatis3.3.1所寫,寫于2016年12月24日星期六)