目錄
<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;
<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;
List<BatchResult> 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<?> 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<DeferredLoad> 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<DeferredLoad>();
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<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
return doFlushStatements(isRollBack);
public <E> List<E> 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 <E> List<E> 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 && ms.isFlushCacheRequired()) {
clearLocalCache();
List<E> list;
queryStack++;
list = resultHandler == null ? (List<E>) 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<?> 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<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (int i = 0; i < 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<BatchResult> doFlushStatements(boolean isRollback)
protected abstract <E> List<E> 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 && 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 <E> List<E> 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<?> 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<?> 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 && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
public void load() {
@SuppressWarnings( "unchecked" )
// we suppose we get back a List
List<Object> list = (List<Object>) 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 <E> List<E> 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.<E>query(stmt, resultHandler);
public List<BatchResult> 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<String, Statement> statementMap = new HashMap<String, Statement>();
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.<E>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) && !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。
<setting name="defaultExecutorType" value="SIMPLE"/>
注意,當Mybatis整合Spring後,Spring掃描後生成的Mapper對象,底層使用的SqlSession都是用的預設的Executor。如果我們需要在程式中使用非預設的Executor時,我們可以在Spring的bean容器中聲明SqlSessionFactoryBean,然後在需要指定Executor的類中注入SqlSessionFactory,通過SqlSessionFactory來建立指定ExecutorType的SqlSession。
參考文檔
Mybatis源碼
(注:本文是基于Mybatis3.3.1所寫,寫于2016年12月24日星期六)