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详解

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