一、Executor
前文分析了SqlSession,总的说来就是实现了数据库操作的增删改查方法,但是具体的调用都是通过Executor来实现的,所以分析Executor对了解Mybatis是怎么运行极其重要。
Executor是SqlSession的构造参数之一,他的实例化过程是调用Configuration的newExecutor方法返回的Executor executor = configuration.newExecutor(tx, execType),tx是事务工厂(已经介绍过),execType是执行器类型(SIMPLE、REUSE、BATCH):
- SIMPLE:默认值,会为每个语句创建新的预处理语句
- REUSE:会重用预处理语句
- BATCH:会批量执行所有的更新语句
具体到源码做进一步分析。
newExecutor(tx, execType):
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;
}
所以,默认会返回CachingExecutor对象。
二、CachingExecutor
顾名思义就是带有缓存器的执行器,也就是查询会缓存起来,增加、删除、修改时会清除缓存
public CachingExecutor(Executor delegate) {
//这里有个委派执行器
this.delegate = delegate;
//委派执行器又会包装当前的缓存执行器
delegate.setExecutorWrapper(this);
}
我们先分析其query查询方法:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取具体的sql绑定对象
BoundSql boundSql = ms.getBoundSql(parameterObject);
//得到缓存的key值
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 = 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);
}
- CacheKey :缓存的主键,分析createCacheKey可知,键值是由查询名称、offset、limit、sql语句、参数值、以及环境id生成。
- TransactionalCacheManager:具体缓存值存放是通过这个来实现的,调用其getObject和putObject方法,底层的存储是利用HashMap来实现,如果执行增删改方法是就会调用其clear方法清除缓存。
说到缓存,就存在一级缓存和二级缓存,一级缓存是SqlSession级别的缓存,只有在同一个session实例下才有效,不同的session不共享缓存,二级缓存是SqlSessionFactory级别,可以不同session共享缓存(mybatis缓存实现将单独分析)。
二、BaseExecutor
经过上述分析可知,具体的操作又是通过委托对象delegate来实现,也就是具体的SIMPLE、REUSE、BATCH等执行器,具体的执行语句是通过他们的父类BaseExecutor实现,如下代码段是其构造方法。
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;
}
继续以查询为例分析:
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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// 如果配置的statementType是STATEMENT那么也要清除缓存
clearLocalCache();
}
}
return list;
}
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 {
localCache.removeObject(key);
}
//添加缓存
localCache.putObject(key, list);
//如果是存储过程,缓存参数
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
doQuery:
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();
//获取执行的handler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//获取statement对象
stmt = prepareStatement(handler, ms.getStatementLog());
//交给handler执行
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
认真分析,可以发现最后都是交给StatementHandler去执行操作,这个后文再进行分析,本文就不继续展开。
三、SIMPLE、REUSE、BATCH执行器的区别
接下来,我们就从源码来分析这三个执行器的区别,SimpleExecutor中就简单的各种do**方法,ReuseExecutor中多了一个Map<String, Statement> statementMap集合属性,REUSE与SIMPLE的区别在于复用预处理语句,他们之间的不同点在于prepareStatement方法的实现。
ReuseExecutor中的prepareStatement方法。
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;
}
可以看出不同点在于REUSE会把sql语句和statement对象用HashMap保存起来,下次执行同样的sql语句的时候就会直接重用之前的Statement对象。
我们再看看BatchExecutor对象,这个对象有五个属性值,这个对象会批量的执行更新语句。
public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;
private final List<Statement> statementList = new ArrayList<>();
private final List<BatchResult> batchResultList = new ArrayList<>();
private String currentSql;
private MappedStatement currentStatement;
SIMPLE和RFEUSE的do**方法,就是一个标准的模板方法设计先获取Configuration、再获取StatementHandler对象,然后实例化Statement对象,最后通过handler执行语句。
BatchExecutor中的doUpdate方法:
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
final Configuration configuration = ms.getConfiguration();
//也是获取statement对象
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;
//先判断当前执行SQL语句和上一条语句是否一样
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));
}
//这里就是不断的添加需要执行的sql语句
handler.batch(stmt);
return BATCH_UPDATE_RETURN_VALUE;
}
我们知道调用addBatch方法后还需要调用executeBatch方法才行,但是这里并没有,所以这个操作就需要在commit方法中去实现,在父类BaseExcutor的commit方法会调用一个doFlushStatements方法,BATCH重写了这个方法,这个方法中就调用stmt.executeBatch(),所以这三类执行器的区别也不难理解。
四、总结
本文简单介绍了Executor,以及SIMPLE、REUSE、BATCH三类执行器的区别,SqlSession拿到具体的执行语句对象,然后交给Executor来执行,执行器又处理好缓存以及是否重用以及批量更新等操作。然后又把具体的执行工作交给了StatemHandler来执行,接下来我们先分析一下Mybatis的缓存机制,然后再继续往后分析。
以上,有任何不对的地方,请指正,敬请谅解。