上一篇对Executor接口进行了大致的分析,这边来针对它的实现进行分析。
现在我们知道,mybatis默认情况下是使用simpleExecutor的,如果你需要修改,有两种方式,一是在setting中配置defualtExecutorType,另一种方式是通过SqlSessionManager指定executorType并创建sqlsession。下面以doUpdate和doQuery方法为例,比较这几种executor的差异,因为executor提供sqlSession的就是这两个核心基础方法。
simpleExecutor:
@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());//创建statement
return handler.update(stmt);
} finally {
closeStatement(stmt);//关闭statement,意味着下一次使用需要重新开启statement
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());//创建statement
handler.parameterize(stmt);//将参数存入statement
return stmt;
}
ReuseExecutor:
将statement存入map中:
private final Map statementMap = new HashMap();
@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);//没有关闭statement
}
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);//如果已缓存statement则取出
applyTransactionTimeout(stmt);
} else {
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
putStatement(sql, stmt);//否则创建statement并存入
}
handler.parameterize(stmt);
return stmt;
}
看看它是怎么映射判断已缓存的statement的:
private boolean hasStatementFor(String sql) {
try {
return statementMap.keySet().contains(sql) && !statementMap.get(sql).getConnection().isClosed();//sql语句就是key,statement是值
} catch (SQLException e) {
return false;
}
}
BatchExecutor:
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;//为何?防无限循环
private final List statementList = new ArrayList();//使用list缓存statement
private final List batchResultList = new ArrayList();
private String currentSql;
private MappedStatement currentStatement;
@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)) {//判断当前使用sql和statement是否是上一次的statement和sql
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);//否则创建statement并存入
batchResultList.add(new BatchResult(ms, sql, parameterObject));
}
// handler.parameterize(stmt);
handler.batch(stmt);//仅仅是存入批处理参数而不执行
return BATCH_UPDATE_RETURN_VALUE;//始终返回一个常量
}
在BatchExecutor中的doupdate并不会想前面两者那样执行返回行数,而是每次执行将statement预存到有序集合,官方说明这个executor是用于执行存储过程的和批量操作的,因此这个方法是循环或者多次执行构建一个存储过程或批处理过程。
simpleExecutor:
没有什么特别,常规操作
@Override
public List 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);//也是关闭statement
}
}
ReuseExecutor:
@Override
public List 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);//不关闭,对缓存的statement进行操作
}
BatchExecutor:
@Override
public List doQuery(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
throws SQLException {
Statement stmt = null;
try {
flushStatements();//刷新statement
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);//关闭statement
}
}
可以看到,batch的执行方式也有所不同,每次执行查询都会执行刷新statement,为何?如下:
@Override
public List doFlushStatements(boolean isRollback) throws SQLException {
try {
List results = new ArrayList();
if (isRollback) {
return Collections.emptyList();
}
for (int i = 0, n = statementList.size(); i < n; i++) {//批量执行有序集合中的statement
Statement stmt = statementList.get(i);
applyTransactionTimeout(stmt);
BatchResult batchResult = batchResultList.get(i);
try {
batchResult.setUpdateCounts(stmt.executeBatch());//执行批处理或存储过程
MappedStatement ms = batchResult.getMappedStatement();
List
其实就是执行doUpdate并不会提交过程或批处理,这个操作只有doQuery才会执行。
到这里,我们可以总结一下SimpleExecutor、ReuseExecutor、BatchExecutor的区别了:
另外还有一个执行器,CachingExecutor,之所以没有加入对比,因为这个执行器不是工作在同一个层面上的,什么时候会使用到他?回过头查看如何在configuration中被创建的:
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
protected boolean cacheEnabled = true;
可以看出默认是开启的,在XMLConfigBuilder中可以判断,这也是存在配置文件中的一个选项,如果没有设置,则默认开启:
configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
下面是官方文档:
除此之外,没有别的地方可以修改这个配置,也就是运行时不可更改。剧透一下,这个其实就是二级缓存。
在上一篇executor里面,提到了CachingExecutor其实是baseExecutor的一个包装,进一步说,也就是对SimpleExecutor、ReuseExecutor、BatchExecutor进行包装,现在来研究下进行了什么样的包装:
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);//刷新缓存
return delegate.update(ms, parameterObject);
}
对于update方法,在executor执行之前,可选择性的清空缓存,换一个角度,如果这里只是清空缓存,那么必定在它之前会有一个步骤是加入缓存,所以我们看下baseExecutor中的实现:
@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);
}
上述代码中,doUpdate是委托,不展开,关键是在这之前执行的clearLocalCache方法,看样子是清空本地的缓存:
@Override
public void clearLocalCache() {
if (!closed) {
localCache.clear();//清空本地
localOutputParameterCache.clear();//清空本地输出
}
}
这两个本地缓存是什么,可以推测就是一级缓存,因为它内置于executor中,而executor内置于一个SqlSession中,所以这个应该就是session级别的缓存:
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
这里不关心缓存是怎么来的,只关心如何处理,因为缓存可能有很多种类型,不会是一个简单的map,如果深究进去,就偏离主题了,在另一篇里面再做缓存解析。
回归主题,从上面的过程判断,缓存在update方法中没有添加,反而可能会清空,其原因一个是对update的执行语句进行缓存意义不大,另一方面就是update之后缓存是过时的,那么在什么时候会触发添加缓存操作呢?结合实际我们实际操作分为update和query两种,也就是说缓存应该是query方法执行的时候触发添加的。而对于query方法,从executor接口中的定义可以看出对应的性质:
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
第一个query的参数定义中具有cachekey和boundsql,可以推测该方法要求实现的是带缓存的操作。这个时候需要注意了,这是接口方法,而CachingExecutor和BaseExecutor都是实现子类,如果他们的实现要求不存在冗余(职责单一),那么使用包装模式是必然的了,这时候应当将看源码的方式从尾查找切换为从根查找,先看BaseExecutor中的的一个query:
@Override
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);//将statement、parameter等参数构建一个CacheKey
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);//执行带缓存的query
}
可以看出,executor设计的两个query方法是考虑重用的,第一个query是可以被第二个query方法重用的,进一步看第二个query,在这之前,会先将一些数据作为CacheKey并创建缓存的key:
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());//可见加入缓存的方式并不是键值对的形式,而是统统丢入其中
List parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {//如果sql有额外的参数则存入参数
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {//否则如果入参为null则存入null
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {//否则如果入参是经过TypeHandler处理则存入
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;
}
通过上面的代码可以发现,executor使用的本地缓存的键是一组数据,包括了statement的id、查询其实行、查询行数、sql、参数等。CacheKey不再本文讨论范围只能,而我们现在已经知道一级缓存的key就是通过这些数据产生的,接下来看第二个query方法如何处理:
@Override
public List 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 list;
try {
queryStack++;//用一个int来标识操作状态
list = resultHandler == null ? (List) localCache.getObject(key) : null;//从缓存中取出查询结果list
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);//如果list存在则输出参数
} else {
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);//list不存在则执行查询
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();//将延迟加载清单中的条目加载
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {//如果范围级别是statement则清空本地缓存
// issue #482
clearLocalCache();
}
}
return list;
}
如果传入了resultHandler,则不会从缓存中查找已查询的结果,如果没有结果,再执行查询,后面还会是根据配置文件条目来判断是否保持缓存,默认开启,见官方说明,实际上使用了取巧的办法,每次都是清空缓存-。-:
最终查询核心工作交给queryFromDatabase方法来完成:
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);//先缓存一个占位符
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);//缓存查询结果list
if (ms.getStatementType() == StatementType.CALLABLE) {//缓存输出参数,仅Callable类型的statement支持
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
经过上面的过程可以推断,实际上,CachingExecutor的query的操作,尤其是一级缓存的操作,在baseExecutor中已经实现,那么CachingExecutor做了什么?来看一下,第一个query和baseExecutor中无异:
@Override
public List 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);
}
第二个query就有区别了:
@Override
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);//如果statement有缓存并且设置为必需刷新则清空二级缓存
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);//确保statement没有输出参数
@SuppressWarnings("unchecked")
List list = (List) 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;//如果list存在则返回二级缓存的list,否则执行查询并缓存结果list
}
}
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
如官方所述那样,一个查询statement如果已被缓存,那么优先使用二级缓存,否则会执行委托executor,也即是baseExecutor的一级缓存。
上面清空缓存的工作由一个事务缓存管理器处理,所以说二级缓存又称为事务缓存:
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();//从mappedstatement中取出缓存对象
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);//清空缓存
}
}
需要注意的是,这种缓存对于Callable类型的statement的输出参数是无效的,而且还会抛出异常,见下:
private void ensureNoOutParams(MappedStatement ms, 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.");
}
}
}
}
到这里,所有的executor解析完毕,下一篇将会对statement进行解析。本篇产出的类关系结构图如下: