Mybatis源码分析(4) -- Executor分析
这一篇我们要分析一下Executor的功能,先看看Executor接口提供了那些功能方法:
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
//执行修改操作
int update(MappedStatement ms, Object parameter) throws SQLException;
//执行查询操作(带缓存key)
<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(boolean required) throws SQLException;
//回滚事务
void rollback(boolean required) throws SQLException;
//创建CacheKey
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();
}
从Executor接口功能来分析,Executor主要提供了以下几个功能:
- 查询、修改数据库操作
- 提供了缓存功能
- 获取事务以及对事务操作
Executor接口的实现图是这样的:
Executor
|-------BaseExecutor(基础抽象执行器)
|-----------SimpleExecutor(简单执行器)
|-----------ReuseExecutor(重用执行器)
|-----------BatchExecutor(批量执行器)
|-------CachingExecutor(缓存执行器)
下面我们逐一分析一下各个实现类,看看各个子类的功能是如何实现的。
BaseExecutor 基础抽象执行器
BaseExecutor是对Executor最基础的实现,这个类比较简单,下面以query操作简单的分析一下。
先看看最常用的query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler)方法。这个方法负责查询操作。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//根据参数对象获取到BoundSql,BoundSql包装了执行的sql和参数信息
BoundSql boundSql = ms.getBoundSql(parameter);
//创建CacheKey用于获取缓存结果(当作hashcode的计算就可以了)
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 (closed) throw new ExecutorException("Executor was closed.");
//判断是否需要清除缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
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--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
deferredLoads.clear(); // issue #601
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache(); // issue #482
}
}
return list;
}
下面是从数据库中获取数据。
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 {
localCache.removeObject(key);
}
//存放缓存结果
localCache.putObject(key, list);
//如果是存储过程语句,则需要缓存输出参数
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
接下来看看子类中的doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)是怎样实现的。
SimpleExecutor 简单执行器
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
//获取Configuration
Configuration configuration = ms.getConfiguration();
////创建StatementHanlder,这里使用了拦截器代理
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
//生成Statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
//真正的查询操作是由StatementHandler来完成的
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取Configuration
Configuration configuration = ms.getConfiguration();
//创建StatementHanlder,这里使用了拦截器代理
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, rowBounds, resultHandler, boundSql);
//生成Statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
//真正的查询操作是由StatementHandler来完成的
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
//创建Statement对象
stmt = handler.prepare(connection);
//为Statement对象设置参数
handler.parameterize(stmt);
return stmt;
}
ReuseExecutor 重用执行器
首先我们要明白,这里的重用指的是什么?这里的重用指的是Statement对象的重用。也就是说,对于相同的sql,如果Statement对象还存在的话,则使用之前存在的Statement对象。
public class ReuseExecutor extends BaseExecutor {
//缓存Statement
private final Map<String, Statement> statementMap = new HashMap<String, Statement>();
public ReuseExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
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对象(可以重用之前存在的Statement)
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
}
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(this, ms, parameter, rowBounds, resultHandler, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}
/**
* 提交事务的时候会调用这个方法,关闭Statement,清除缓存
*/
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
for (Statement stmt : statementMap.values()) {
closeStatement(stmt);
}
statementMap.clear();
return Collections.emptyList();
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
//如果Statement没有被关闭,则从缓存中获取,重新使用Statement
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
} else {
//创建新的Statement,并放入到缓存中去
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
putStatement(sql, stmt);
}
//设置预处理参数
handler.parameterize(stmt);
return stmt;
}
private boolean hasStatementFor(String sql) {
try {
//判断sql是否被缓存,并且该sql缓存的Statement没有被关闭
return statementMap.keySet().contains(sql) && !statementMap.get(sql).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);
}
}
BatchExecutor 批量执行器
在讲解BatchExecutor先熟悉一下JDBC中是怎么进行批量处理的,另外,批量操作只对插入、修改、删除有效。下面看看Statment和PreparedStatement的批量操作流程:
Statement
- addBatch(String sql)方法会在批处理缓存中加入一条sql语句。<br>
- executeBatch()执行批处理缓存中的所有sql语句。<br>
//创建Statment对象
Statment statment = con.createStatement();
//添加第一个语句
statment.addBatch("insert into student values(23,'tangbao','高数',100)");
//添加第二个语句
statment.addBatch("insert into student values(24,'王定','c#',98)");
//批量执行
statment.executeBatch();
PreparedStatement
- 设置参数
- addBatch()将一组参数添加到PreparedStatement对象内部。
- executeBatch()将一批参数提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。
//创建PreparedStatement对象
PreparedStatement preparedStatment = con.prepareStatement("insert into student values(?,?,?,?)");
//设置第一个参数,提交批量
preparedStatment.setInt(1, 33);
preparedStatment.setString(2,"wangqin");
preparedStatment.setString(3, "c++");
preparedStatment.setDouble(4, 78.5);
pstm.addBatch();
//设置第二个参数,提交批量
preparedStatment.setInt(1, 34);
preparedStatment.setString(2,"wuytun");
preparedStatment.setString(3, "c");
preparedStatment.setDouble(4, 77);
pstm.addBatch();
//批量执行
pstm.executeBatch();
关于JDBC批量操作的流程大致就是上面这样的了,接下来我们看看BatchExecutor是如何实现的。
public class BatchExecutor extends BaseExecutor {
//批量修改时的返回值(感觉这个没有什么用)
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
//保存执行Statment集合
private final List<Statement> statementList = new ArrayList<Statement>();
//保存批量操作后的结果集合
private final List<BatchResult> batchResultList = new ArrayList<BatchResult>();
//最近一次执行的语句
private String currentSql;
//最近一次执行语句的MappedStatment对象
private MappedStatement currentStatement;
public BatchExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
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;
//如果执行语句和最近一次相同,则获取到上次的Statment和BatchResult,并把参数对象设置到BatchResult中
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
stmt = statementList.get(last);
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
//创建新的Statment和对应的BatchResult
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection);
currentSql = sql;
currentStatement = ms;
statementList.add(stmt);
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
//设置参数
handler.parameterize(stmt);
//调用批量,这里就是调用了Statment#addBatch(sql)或PreparedStatement#addBatch()方法
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
public <E> List<E> doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
//刷新Statment,也就是调用下面的doFlushStatements(false),先批量执行掉之前的缓存语句操作
flushStatements();
//查询操作不涉及批量操作,和SimpleExecutor中的处理一样
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameterObject, rowBounds, resultHandler, boundSql);
Connection connection = getConnection(ms.getStatementLog());
stmt = handler.prepare(connection);
handler.parameterize(stmt);
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
try {
List<BatchResult> results = new ArrayList<BatchResult>();
if (isRollback) {
return Collections.emptyList();
} else {
//遍历Stament
for (int i = 0, n = statementList.size(); i < n; i++) {
//获取到存储的Statment和对应的BatchResult
Statement stmt = statementList.get(i);
BatchResult batchResult = batchResultList.get(i);
try {
//调用Statment#executeBatch()或PreparedStatement#executeBatch()方法批量执行,并将执行结果设置到BatchResult中去。
batchResult.setUpdateCounts(stmt.executeBatch());
MappedStatement ms = batchResult.getMappedStatement();
List<Object> parameterObjects = batchResult.getParameterObjects();
KeyGenerator keyGenerator = ms.getKeyGenerator();
if (keyGenerator instanceof Jdbc3KeyGenerator) {
Jdbc3KeyGenerator jdbc3KeyGenerator = (Jdbc3KeyGenerator) keyGenerator;
jdbc3KeyGenerator.processBatch(ms, stmt, parameterObjects);
} else {
for (Object parameter : parameterObjects) {
keyGenerator.processAfter(this, ms, stmt, parameter);
}
}
} catch (BatchUpdateException e) {
StringBuffer message = new StringBuffer();
message.append(batchResult.getMappedStatement().getId())
.append(" (batch index #")
.append(i + 1)
.append(")")
.append(" failed.");
if (i > 0) {
message.append(" ")
.append(i)
.append(" prior sub executor(s) completed successfully, but will be rolled back.");
}
throw new BatchExecutorException(message.toString(), e, results, batchResult);
}
//添加批量结果
results.add(batchResult);
}
//返回批量结果集合
return results;
}
} finally {
//关闭掉Stament对象,并清空存储的Statment和BatchResult集合
for (Statement stmt : statementList) {
closeStatement(stmt);
}
currentSql = null;
statementList.clear();
batchResultList.clear();
}
}
}
CachingExecutor
首先,我们要清楚CachingExecutor是直接实现Executor接口的,并没有继承BaseExecutor。CacheingExecutor只是委派了一个Executor来执行,也就是委派给BaseExecutor的具体实现类来处理。很显然这里使用了设计模式中的包装模式,将BaseExecutor进行包装处理,添加了缓存功能。
public class CachingExecutor implements Executor {
//被包装的BaseExecutor
private Executor delegate;
//是否自动提交
private boolean autoCommit;
//事务缓存管理器,管理事务的提交回滚
private TransactionalCacheManager tcm = new TransactionalCacheManager();
//缓存功能开关标志位
private boolean dirty;
public CachingExecutor(Executor delegate) {
this(delegate, false);
}
public CachingExecutor(Executor delegate, boolean autoCommit) {
this.delegate = delegate;
this.autoCommit = autoCommit;
}
public Transaction getTransaction() {
return delegate.getTransaction();
}
public void close(boolean forceRollback) {
try {
//如果缓存关闭并且不需要自动提交,则进行回滚
if (dirty && !autoCommit) {
tcm.rollback();
} else {
//自动提交
tcm.commit();
}
} finally {
//关闭事务
delegate.close(forceRollback);
}
}
public boolean isClosed() {
return delegate.isClosed();
}
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//刷新缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
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);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
//Cache对象是在什么时候设置的呢?
Cache cache = ms.getCache();
if (cache != null) {
//刷新缓存
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, parameterObject, boundSql);
if (!dirty) {
cache.getReadWriteLock().readLock().lock();
try {
//从缓存中获取数据,如果存在则返回
List<E> cachedList = (List<E>) cache.getObject(key);
if (cachedList != null) return cachedList;
} finally {
cache.getReadWriteLock().readLock().unlock();
}
}
//如果缓存开关关闭(dirty=true),或者缓存中没有获取到数据,则从数据据查询数据
List<E> list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//将查询的结果放入到缓存中去
tcm.putObject(cache, key, list);
//返回查询数据
return list;
}
}
//不使用缓存功能,直接从数据库查询数据
return delegate.<E>query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public List<BatchResult> flushStatements() throws SQLException {
return delegate.flushStatements();
}
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
dirty = false;
}
public void rollback(boolean required) throws SQLException {
try {
delegate.rollback(required);
dirty = false;
} finally {
if (required) {
tcm.rollback();
}
}
}
//针对存储过程调用时的检测
private void ensureNoOutParams(MappedStatement ms, Object parameter, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
throw new ExecutorException("Caching stored procedures with OUT params is not supported. Please configure useCache=false in " + ms.getId() + " statement.");
}
}
}
}
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
public boolean isCached(MappedStatement ms, CacheKey key) {
throw new UnsupportedOperationException("The CachingExecutor should not be used by result loaders and thus isCached() should never be called.");
}
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
throw new UnsupportedOperationException("The CachingExecutor should not be used by result loaders and thus deferLoad() should never be called.");
}
public void clearLocalCache() {
delegate.clearLocalCache();
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
//如果既有缓存对象,又需要刷新缓存,则标志缓存功能关闭
if (cache != null && ms.isFlushCacheRequired()) {
dirty = true;
tcm.clear(cache);
}
}
}
Executor的源码我们都已经分析的差不多了,Executor接口有四中具体的子类,那么,Mybatis应该使用哪一种呢?或者说怎样才能指定使用哪一种Executor呢?关于Executor的创建过程可以查看 Mybatis源码分析(1) -- SqlSession的创建。
另外,在上面分析doQuery方法时发现Cache cache = ms.getCache(),只有cache不为null是才能进行缓存查询。那么,cache是什么时候设置的呢? 这里的ms就是MappedStatement对象啦,所以我们会到之前分析MappedStatement创建的地方。MappedStatement是由MapperBuilderAssistant#addMappedStatement()创建的,MappedStatement的创建在前面已经分析过了,就不赘述了。可以参考Mybatis源码分析(3) -- Mapper文件解析。在创建MappedStatment时调用了MapperBuilderAssistant#setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder)用来创建Cache对象。方法中的currentCache就是ms.getCache()获取的Cache对象。关于curretnCache设置则是在如下的方法中:
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
Properties props) {
typeClass = valueOrDefault(typeClass, PerpetualCache.class);
evictionClass = valueOrDefault(evictionClass, LruCache.class);
//创建Cache对象
Cache cache = new CacheBuilder(currentNamespace)
.implementation(typeClass)
.addDecorator(evictionClass)
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.properties(props)
.build();
//添加到Configuration
configuration.addCache(cache);
//设置currentCache
currentCache = cache;
return cache;
}
关于什么时候会设置Cache就不详细的说明了。在配置了<cache>
或@CacheNamespace注解的情况下会去设置Cache对象。可以参考Mybatis源码分析(3) -- Mapper文件解析。
对于ms.isUseCache()和ms.isFlushCacheRequired()的说明,我再多说几句。select语句userCache=true,flushCache=false,对于insert、update、delete语句userCache=false,flushCache=true。
最后,整理一下CachingExecutor的思路:
Cache为空
也就是没有设置<cache>
或@CacheNamespace时,CachingExecutor会按照delegate来执行,相当于没有被包装处理。
Cache不为空
调用flushCacheIfRequired,检测是flushCache,如果为true则设置dirty=true,很明显对于insert、update、delete语句都不会使用缓存功能。所以说CachingExecutor是针对处理select语句的。