这一篇我们再来梳理Mybatis中的Executor接口及其对应的实现类。
上一篇我们主要了解的是StatementHanlder,现在我们来了解另一个接口Executor执行器,前面的StatementHanlder就是在这里创建以及使用的。
一、Executor的结构
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;
<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) 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(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
这里定义了一些sql执行的方法,事务相关等的内容,下面我们集体来看其的实现类是怎样实现的。
二、BaseExecutor
1、结构
public abstract class BaseExecutor implements Executor {
这个就是关于Executor实现相关的基类了。
2、成员变量
1)、transaction
protected Transaction transaction;
这个是对应的事务操作的,例如事务回滚。
2)、wrapper
protected Executor wrapper;
这个也是一个Executor,所以我们可以在一个Executor中包括另一个Executor。
3)、deferredLoads
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
这个是与延迟操作相关的,我们忽略,目前还没有遇到用mybatis主动去进行这种操作。
4)、queryStack
protected int queryStack;
看mybatis对其的自加操作,我觉得其实可以用这个值来判断对db查询操作的并发数量。
还有其它的几个与缓存相关的内容,就不列举了。
3、构造方法
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
这个就是创建对前面的变量的初始化,可以看到目前wrapper的值直接是this。
1)、getTransaction()
@Override
public Transaction getTransaction() {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return transaction;
}
获取Transaction。
2)、 close(boolean forceRollback)
@Override
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;
}
}
这个是关闭当前的执行器,然后入参forceRollback,是表示,在关闭的时候是否直接回滚事务。
3)、isClosed()
@Override
public boolean isClosed() {
return closed;
}
判断当前执行器是否关闭。
4)、update(MappedStatement ms, Object parameter)
@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);
}
这个就是执行更新操作,可以看到其的入参是MappedStatement,以及需要传给sql语句的参数。这里会先去清下缓存
5)、query(…)
@Override
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);
}
查询操作,调用的另一个query方法。
6)、query(…)
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());
......
List<E> list;
try {
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);
}
} finally {
queryStack--;
}
......
return list;
}
上面我省略了一些关于延迟加载&缓存相关的内容。可以看到其首先是从缓存中去获取,没有的话再调用queryFromDatabase方法去查询DB。这里我们可以看到一个其有一个try-finally,首先会queryStack++,然后在queryStack–,同时queryStack是成员变量,所以其可以统计总是查询峰值(当然可能由于并发出现少统计的情况)。
7)、queryCursor(…)
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return doQueryCursor(ms, parameter, rowBounds, boundSql);
}
这个就是去执行存储过程,我们忽略。
8)、commit(boolean required)
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
提交事务。
9)、rollback(boolean required)
@Override
public void rollback(boolean required) throws SQLException {
if (!closed) {
try {
clearLocalCache();
flushStatements(true);
} finally {
if (required) {
transaction.rollback();
}
}
}
}
回滚事务及前缓存。flushStatements方法一般是供批量操作的Executor使用的。
10)、setExecutorWrapper(Executor wrapper)
@Override
public void setExecutorWrapper(Executor wrapper) {
this.wrapper = wrapper;
}
设置wrapper,其默认是this。
11)、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 {
......
return list;
}
调用doQuery方法去查询DB。
12)、getConnection(Log statementLog)
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
获取Connection。
13)、抽象方法
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;
然后我们看到这些方法都是抽象方法供其子类实现。
三、SimpleExecutor
1、结构&构造方法
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
其继承BaseExecutor,然后直接代用的其的父类构造方法。
2、方法
1)、doUpdate(MappedStatement ms, Object parameter)
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
可以看到其在这里创建我们上一篇梳理的StatementHandler,再通过prepareStatement方法获取Statement,然后再通过StatementHandler去调用Statement的执行方法。然后调用StatementHandler的update去通过其的Statement去执行。
2)、newStatementHandler(…) Configuration
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,这里也会去执行Interceptor。而RoutingStatementHandler我们上篇梳理过,其是根据StatementType创建对应的StatementHandler,所以我们这里一般创建的是SimpleStatementHandler。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
3)、prepareStatement(StatementHandler handler, Log statementLog)
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
这里就是先获取Connection,然后再通过StatementHandler获取初始化创建Statement,再用StatementHandler去处理对应的参数设置。
4)、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.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
这里的主要流程与前面的doUpdate方法类似,不过其调用的是StatementHandler的query方法。
四、BatchExecutor
这个是用来批处理的,所以其主要是用来解析更新操作去进行批处理。这里需要先了解JDBC关于批处理的操作,主要是两个方法:addBatch&executeBatch。
1、结构&构造方法
public class BatchExecutor extends BaseExecutor {
public BatchExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
2、成员变量
1)、statementList
private final List<Statement> statementList = new ArrayList<>();
这个就是用来放批处理的statementList的。
2)、batchResultList
用来放批处理的结果的。
3)、currentSql
private String currentSql;
当前的sql语句。
4)、currentStatement
private MappedStatement currentStatement;
这个就是与对应sql执行需要的整体内容信息。
3、方法
1)、doUpdate(MappedStatement ms, Object parameterObject)
@Override
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
final StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
final String sql = boundSql.getSql();
final Statement stmt;
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
applyTransactionTimeout(stmt);
handler.parameterize(stmt);// fix Issues 322
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt); // fix Issues 322
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
这个就是对应的批处理更新操作。首先是获取StatementHandler,然后通过sql.equals(currentSql) && ms.equals(currentStatement)判断批处理操作时同一个Statement,再从获取statementList获取Statement,再获取对应的批处理的
BatchResult返回对象。
public class BatchResult {
private final MappedStatement mappedStatement;
private final String sql;
private final List<Object> parameterObjects;
......
public void addParameterObject(Object parameterObject) {
this.parameterObjects.add(parameterObject);
}
上面梳理的是能获取到当前的currentSql,如果不能获取到,就需要再创建对应的statement&BatchResult。这些解决了,最后才是一
handler.batch(stmt)去调用batch操作。
@Override
public void batch(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.addBatch();
}
同时通过这个doUpdate方法的获取,我们知道可能会出现线程安全的问题(currentSql、statementList这些都是全局变量,而获取的时候是直接获取的最后一个位置,我再想这里是不是可以使用Map比List要好点?),所以需要自己进行同步(因为这个Executor一般是SqlSession级别的)。
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
这个地方先剧透出来。
2)、doQuery(…)
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameterObject, rowBounds, resultHandler, boundSql);
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
这里与SimpleExecutor是类似的,但其前面都一个关键的方法flushStatements()的调用,BatchExecutor会特别处理。
3)、doFlushStatements(boolean isRollback)
@Override
public List<BatchResult> flushStatements() throws SQLException {
return flushStatements(false);
}
public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
return doFlushStatements(isRollBack);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<>();
if (isRollback) {
return Collections.emptyList();
}
for (int i = 0, n = statementList.size(); i < n; i++) {
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
KeyGenerator keyGenerator = ms.getKeyGenerator();
......省略一些主键操作内容
// Close statement to close cursor #1109
closeStatement(stmt);
} catch (BatchUpdateException e) {
......
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
results.add(batchResult);
}
return results;
} finally {
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
这里就会执行前面在这些addBatch时并没有最好执行executeBatch方法。这里就是遍历statementList,然后获取对应的batchResultLis来得到对应的BatchResult批量结果内容,然后在通过stmt.executeBatch()执行批量操作并得到对应的执行影响结果添加到batchResultList中,所以在使用批量处理的时候,在一直调用update进行批量添加操作的时候,最后要主动调用flushStatements()方法来执行及获取结果,当然也可以执行下查询操作来自动执行这个flushStatements方法。
五、CachingExecutor
1、结构&构造方法
public class CachingExecutor implements Executor {
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
这个与前面两个执行器的实现不同的是,这个是有一个Executor来作为代理实现的。因为这个是使用缓存的执行器,其本身并没有实现一些操作逻辑,其的操作逻辑是交给delegate代理执行器来处理,其主要是处理一些与缓存相关的内容。
2、成员变量
1)、delegate
private final Executor delegate;
这个就是用来执行对应操作的delegate执行器。
2)、tcm
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
这个是与事务想关的缓存。
3、方法
1)、update(MappedStatement ms, Object parameterObject)
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
例如这里的更新操作,其就是通过代理执行器去处理,其本身是调用flushCacheIfRequired方法去处理缓存。
2)、query(…)
@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);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
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);
}
这个查询方法也是类似。
3)、flushCacheIfRequired(MappedStatement ms)
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
这个就是与缓存清理相关的逻辑,我们就不具体分析了。
六、ReuseExecutor
1、结构&构造方法
public class ReuseExecutor extends BaseExecutor {
public ReuseExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
也是直接调用的父类。这个执行器是可重用的执行器,其是对Statement重用,在使用后并不会直接关闭,而是下一次再重新执行。
2、变量
1)、statementMap
private final Map<String, Statement> statementMap = new HashMap<>();
其就是通过这个map来缓存Statement的。
3、方法
1)、doUpdate(MappedStatement ms, Object parameter)
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
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);
}
这个其实与前面的SimpleExecutor方法类似,其主要是没有关闭Statement。下年我们来看下prepareStatement方法的逻辑。
2)、prepareStatement(StatementHandler handler, Log statementLog)
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
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);
}
这个就是prepareStatement对于Statement的获取逻辑。其首先是通过BoundSql来获取对应的sql语句,然后以其为key来看statementMap有没有,有的话就再来使用它,没有的话,就去创建Statement,然后放到statementMap中。
3)、doQuery(…)
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
}
治理的query方法也主要是没有关闭Statement。
七、执行执行器的使用情况
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
通过这个使用代码我们也明白了,就不再赘叙了。需要注意的是,这里也有调用interceptorChain.pluginAll(executor)。