Mybatis的执行器Executor,在创建SqlSession时由SqlSessionFactory注入到SqlSession中。SqlSession把执行sql和返回结果集委托给了Executor,Executor有3中类型 : SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新,我们在mybatis核心配置文件< setting>节点中可以配置默认的执行器
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
在SqlsessionFactory中,由Configuration取得Executor注入到SqlSession中,详细请看Mybatis 源码分析一 SqlSessionFactory
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
execType是一个枚举实例,XMLConfigBuilder读取< setting name=“defaultExecutorType” value=“SIMPLE”/> 把value值设置为默认执行器
//如果defaultExecutorType,未设置取"SIMPLE"
String value = props.getProperty("defaultExecutorType", "SIMPLE"));
//取得枚举实例
ExecutorType et = ExecutorType.valueOf(value)
configuration.setDefaultExecutorType(et);
configuration.newExecutor(tx, execType);取得一个执行器,我们点进去看看他的逻辑
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//默认取SIMPLE
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//根据配置的执行器类型不同,new不同的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来装饰基本执行器
public class CachingExecutor implements Executor {
//真正执行数据库操作的executor
private Executor delegate;
//管理缓存的TransactionalCacheManager
private TransactionalCacheManager tcm = new TransactionalCacheManager();
public void close(boolean forceRollback) {
try {
//issues #499, #524 and #573
if (forceRollback) {
tcm.rollback();
} else {
tcm.commit();
}
} finally {
delegate.close(forceRollback);
}
}
//接受一个目标执行器
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
//执行更新操作会刷新缓存
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, CacheKey key, BoundSql boundSql)
throws SQLException {
//接口所映射的xml是否开启二级缓存
//MyBatis在解析mapper.xml的时候会将cache标签解析为对应的Cache对象,用作二级缓存
Cache cache = ms.getCache();
if (cache != null) {
//是否刷新缓存看<select>节点的配置
flushCacheIfRequired(ms);
//节点是否开启二级缓存,查询默认为true,update 默认false,是否配置了resultHandler
if (ms.isUseCache() && resultHandler == null) {
//如果sql中调用的是存储过程,验证其规则,不允许out类型数据开启缓存
ensureNoOutParams(ms, parameterObject, boundSql);
//从缓存中取出结果集
List<E> list = (List<E>) tcm.getObject(cache, key);
//如果没有缓存,执行查询(默认会走一级缓存)并放入二级缓存
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578. Query must be not synchronized to prevent deadlocks
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
private void ensureNoOutParams(MappedStatement ms, Object parameter, BoundSql boundSql) {
//是否是存储过程
if (ms.getStatementType() == StatementType.CALLABLE) {
//遍历参数
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
//不支持用out参数缓存存储过程
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.");
}
}
}
}
}
如上我们可以看到update是会刷新缓存的,而我们从缓存中取出数据以及存储缓存需要Cache,CacheKey这两个实例,从TransactionalCacheManager(事务缓存管理器)中取出
public class TransactionalCacheManager {
//key为二级缓存实例,value 事务缓存
private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
//清除事务缓存
public void clear(Cache cache) {
getTransactionalCache(cache).clear();
}
//取得缓存
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
//所有事务缓存提交
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
//所有事务缓存回滚
public void rollback() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.rollback();
}
}
//取得事务缓存
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
事务缓存管理器里面持有了一个hashMap,key 为二级缓存,value 为事务缓存,TransactionalCache和二级缓存一样,实现了Cache接口,因为执行事务数据可能回滚,所以不能将数据直接存入二级缓存,可以先存在事务缓存中,commit后再放置二级缓存。我们可以看到当缓存执行器close时,事务缓存会执行commit,或者rollback 。 我们从缓存中取出数据时,调用TransactionalCache的getobject方法,先根据key 也就是二级缓存取出事务缓存,事务缓存会判断该事务是否提交或者关闭,如果该事务还在执行,则返回null,commit或者close了则会根据Cachekey从二级缓存中取出值
,
二级缓存是一个HashMap,是用CacheKey 作为他的键,查询的结果集做为值, 在cache中唯一确定一个缓存项需要使用缓存项的key,Mybatis中因为涉及到动态SQL等多方面因素,
其缓存项的key不能仅仅通过一个String表示,所以MyBatis 提供了CacheKey类来表示缓存项的key,在一个CacheKey对象中可以封装多个影响缓存项的因素
//CachingExecutor 的query方法,当核心配置文件开启二级缓存时创建CacheKey
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
//调用的时BaseExecutor的createCacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
/**
* BaseExecutor: BaseExecutor是一个实现了Executor接口的抽象类,定义若干抽象方法,在执行的时候,把具体的操作委托给子类进行执行。
* */
public abstract class BaseExecutor implements Executor {
/**
* 获取一个缓存key的实例,
* */
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()); //节点的id
cacheKey.update(rowBounds.getOffset()); //分页的rows
cacheKey.update(rowBounds.getLimit()); //分页的page
cacheKey.update(boundSql.getSql()); //sql
//动态sql中所有的jdbcType,javaType,mode等信息
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
//typeHandle信息
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic
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);
}
}
return cacheKey;
}
}
可以看到,节点的id,分页的rows,page, sql,参数,动态sql中所有的jdbcType,javaType,mode等信息,都影响着cachekey的行为
从缓存中没有取得数据时,就需要执行sql来取数据了。mybatis得3种执行器 SimpleExecutor,BatchExecutor,ReuseExecutor ,他们都继承了BaseExecutor
1.BaseExecutor
/**
* BaseExecutor: BaseExecutor是一个实现了Executor接口的抽象类(模板),定义若干抽象方法,在执行的时候,把具体的操作委托给子类进行执行。
* */
public abstract class BaseExecutor implements Executor {
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;
}
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);
}
/**
* @param BoundSql : 由MappedStatement得到,BoundSql内存储了执行的sql语句和命名参数
* */
@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.");//判断是否已经关闭Executor
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//从一级缓存中取出,设置了resultHandler缓存是不会用一级缓存
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;
}
}
2.SimpleExecutor
/**
* simpleExecutor的特点 : 每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。(可以是Statement或PrepareStatement对象)
* simpleExecutor的主要两个功能doQuery和doUpdate insert,delete 也都是update
*/
public class SimpleExecutor extends BaseExecutor {
/**
* 执行修改数据库数据时调用的类
* @param MappedStatement mappenStatement 就是Mapper的一个节点封装的对象
* @param Object Sql的参数
* */
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler((Executor) this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
}
/**
* @param MappedStatement 封装了查询节点的对象,
* @param parameter 参数
* @param RowBounds mybatis自带的分页类
* @param ResultHandler 结果集处理器
* @param BoundSql 处理完任何动态内容后,用于存储实际执行的sql,参数等
* */
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();
//通过configuration取得RoutingStatementHandler,
StatementHandler handler = configuration.newStatementHandler((Executor) wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//取得Statement
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
SimpleExecutor基本就是直接调用预编译对象来执行sql了
3.BatchExecutor
/**
* batch模式重复使用已经预处理的语句,并且批量执行所有更新语句,显然batch性能将更优,
* 但batch模式也有自己的问题,比如在Insert操作时,在事务没有提交之前,是没有办法获取到自增的id
* 设置ExecutorType.BATCH原理:把SQL语句发个数据库,数据库预编译好,数据库等待需要运行的参数,接收到参数后
* 一次运行,ExecutorType.BATCH只打印一次SQL语句,多次设置参数步骤,
*/
public class BatchExecutor extends BaseExecutor {
/**
* 当同一个Sqlsession实例执行update(包含了insert,delete)时,如果执行的是同一个语句,将执行批处理操作,
* 如果语句有A,B 先执行A 10次,再执行B 10次,最后再执行A 10次,那么将执行3次批处理操作,因为只从尾部取出上一次的statement对象
*/
public int doUpdate(MappedStatement ms, Object parameterObject) throws SQLException {
//取得Configuration
final Configuration configuration = ms.getConfiguration();
//取得StatementHandler
final StatementHandler handler = configuration.newStatementHandler((Executor) this, ms, parameterObject, RowBounds.DEFAULT, null, null);
final BoundSql boundSql = handler.getBoundSql();
//取得Sql
final String sql = boundSql.getSql();
final Statement stmt;
//如果sql是上次执行得sql,mapperStatement是上次得mapperStatement
if (sql.equals(currentSql) && ms.equals(currentStatement)) {
int last = statementList.size() - 1;
//取得最后Statement对象
stmt = statementList.get(last);
//参数放在最后的batchResult实例上
BatchResult batchResult = batchResultList.get(last);
batchResult.addParameterObject(parameterObject);
} else {
//如果不存在就创建一个批处理操作
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);
//调用jdbc的批处理操作
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 {
//执行query将刷新批处理,清除存储批处理的容器,
flushStatements();
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler((Executor) wrapper, 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);
}
}
}
4,ReuseExecutor
/**
* 把sql作为key,statement为value,存入hashMap中,如果sql相同,从map中取得statement
*/
public class ReuseExecutor extends BaseExecutor {
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler((Executor) this, ms, parameter, RowBounds.DEFAULT, null, null);
//取得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((Executor) wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//取得Statement
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql();
//如果有相同sql,从容器中取出statement
if (hasStatementFor(sql)) {
stmt = getStatement(sql);
} else {
//否则,取得新的statement,放入map中
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection);
putStatement(sql, stmt);
}
handler.parameterize(stmt);
return stmt;
}
}
总结 : CacheExecutor 是一个装饰器,使被装饰的类具有二级缓存的功能,当mybatis读取核心配置文件将mapper接口和xml封装为MapperStatement 时就为每个mapperStatement构建了对应的cache,所以二级缓存时是对应mapperStatement,但CacheExecutor里的TransactionalCacheManager是可以清除或者添加数据进二级缓存,在执行update时,会清除所有的缓存,而不是更具cachekey来清除固定的value 。
BaseExecutor :是一个模板类 封装了一级缓存和延迟加载的功能,但查询功能却委托给了不同的子类。一级缓存好像有些鸡肋,谁会没事在同一个sqlSession里面查询两次相同的sql并且参数相同啊,但他默认开启,如果二级缓存开启的话,取得的值将会放在二级缓存当中。延迟加载是一个很复杂的功能,我们以后再谈论。
BatchExecutor : 批处理的执行器,在执行update时(当然包括了insert,delete)会将创建的编译对象存储在map中,每次有新的update语句进来时会取map得最后一个key来判断是否时相同得sql,mapperStatement,如果相同就加入批处理之中。这对于大批量得对一个表里插入数据来说能很明显得提升性能。但在执行query时,并不会有性能得提升,却会刷新update中得map
ReuseExecutor : 重用执行器,创建了编译对象就存入map中,如果下次执行相同得sql,就从缓存中取出编译对象,如果执行相同得sql,参数不同,ReuseExecutor 还是有点用得
SimpleExecutor : 每次都创建新得编译对象,没什么好说的
[上一篇]: Mybais 源码分析二 SqlSession