Executor及缓存
mybatis提供了缓存查询结果的功能,缓存功能主要由Executor来提供,所以下面首先看下Executor接口
Executor
// 执行update delete等操作
int update(MappedStatement ms, Object parameter) throws SQLException;
// 执行select
<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;
// 批量执行sql
List<BatchResult> flushStatements() throws SQLException;
// 提交事务
void commit(boolean required) throws SQLException;
// 回滚事务
void rollback(boolean required) throws SQLException;
// 创建缓存中的Key
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);
BaseExecutor
BaseExecutor实现了Executor的所有方法,并且通过模板设计模式,将query,update,queryCursor,flushStatements等方法真正从数据库中执行对应操作的逻辑抽出来,由其子类实现
这里看下query方法
重要属性
// 事务
protected Transaction transaction;
// 延迟加载相关
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 本地缓存
protected PerpetualCache localCache;
// 本地缓存,缓存存储过程中的输出参数
protected PerpetualCache localOutputParameterCache;
// 全局配置
protected Configuration configuration;
// 当前的查询嵌套层数
protected int queryStack;
// 是否已经关闭
private boolean closed;
一级缓存
BaseExecutor中实现了对一级缓存的操作
query
首先看下BaseExecutor的query方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取使用占位符?替换之后的sql
BoundSql boundSql = ms.getBoundSql(parameter);
// 创建缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 调用重载方法进行查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
createCacheKey
下面看下createCacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
// 1. 使用MappedStatement的id更新cacheKey
cacheKey.update(ms.getId());
// 2. 使用rowbounds的offset和Limit来更新cacheKey
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// 3. 使用boundSql中已经使用?替换后的sql来更新cacheKey
cacheKey.update(boundSql.getSql());
List<ParameterMapping> 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)) {
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
cacheKey.update(value);
}
}
// 使用enviroment的id来更新cacheKey
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
在更新cacheKey的过程中,主要是使用入参的hash值来更新cacheKey,同时会将入参放到一个列表中
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
综上所述,cacheKey的组成来自如下几个部分:
- MappedStatement的id
- Rowbounds的offset和limit
- BoundSql解析后的sql
- 输入参数的值
- Environment的key
下面看下CacheKey的equals方法
public boolean equals(Object object) {
// 1. 比较引用
if (this == object) {
return true;
}
// 2. 比较类型
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
// 3. 比较hashCode
if (hashcode != cacheKey.hashcode) {
return false;
}
// 4. 比较checkSum
if (checksum != cacheKey.checksum) {
return false;
}
// 5. 比较cacheKey更新的次数
if (count != cacheKey.count) {
return false;
}
// 6. 依次比较每个入参
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
一级缓存查询
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.");
}
// queryStask记录嵌套查询的层数
// 如果当前不是嵌套查询,并且在select标签上配置了flushCache,那么会清空一级缓存
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();
// 当前一级缓存的级别是STATEMENT的,缓存只在此次statement中有效,执行完毕就会清空缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
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;
// 首先会在缓存中增加一个当前key的占位符
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;
}
update
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);
}
一级缓存总结
我们知道每个SqlSession都包含一个Executor,每个Executor自己维护一份属于自己的一级缓存,因此一级缓存是SqlSession级别的,SqlSession之间不会共享缓存
二级缓存
从之前分析mybatis执行流程的源码中,我们知道,在获取SqlSession时,会重新创建一个Executor
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
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);
}
// 这里会判断是否开启了缓存
// 这里是根据mybatis-config.xml中的cacheEnabled属性来判断是否开启二级缓存的
if (cacheEnabled) {
// 通过装饰者设计模式来对Executor的功能进行增强,增加二级缓存的处理逻辑
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
query
下面看下CacheExecutor的query方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 首先解析BoundSql
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 {
// 获取当前MappedStatement使用的缓存对象
// 从这里可以看出缓存是和MappedStatement绑定的,而MappedStatment是全局共享的,因此二级缓存是全局范围的
Cache cache = ms.getCache();
if (cache != null) {
// 判断MappedStatement是否配置了flushCache,如果配置了,会清空二级缓存
flushCacheIfRequired(ms);
// 当前MappedStatement使用缓存,并且没有配置ResultHandler
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 获取缓存,这里需要注意并没有直接从Cache对象中获取缓存,而是通过TransactionalCacheManager
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
// 如果缓存为空,那么会将请求委托给包裹的Executor,一般是BaseExecutor的子类,因此会经过一级缓存
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 将查询结果添加到缓存中,这里也需要注意并不是将数据直接添加到Cache中,而是对TransactionalCacheManager进行操作
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
update
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
// 在将请求交给delegate之前,先清空缓存
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
// 使用TransactionalCacheManager来清空缓存
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
commit
@Override
public void commit(boolean required) throws SQLException {
// 首先使用被包裹的Executor来进行提交
delegate.commit(required);
// 然后执行TransactionalCacheManager的提交
tcm.commit();
}
缓存对象
那么上面使用到的MappedStatement的缓存对象是从哪里来的呢
这里可以看下MappedStatement的解析过程
private void configurationElement(XNode context) {
try {
// 省略
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
// 省略
);
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
在解析Mapper时,会解析cache-ref和cache
cache-ref配置了当前命名空间使用哪个命名空间的缓存对象
cache-ref的解析过程也比较简单,就是在当前Mapper的解析辅助类中设置了引用的cache所属的命名空间
private void cacheRefElement(XNode context) {
if (context != null) {
configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
try {
cacheRefResolver.resolveCacheRef();
} catch (IncompleteElementException e) {
configuration.addIncompleteCacheRef(cacheRefResolver);
}
}
}
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}
cache则是创建属于自己的命名空间
private void cacheElement(XNode context) {
if (context != null) {
// 缓存对象的类型,默认是PERPETUAL
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
// 缓存清退策略
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
// 刷新间隔
Long flushInterval = context.getLongAttribute("flushInterval");
// 缓存大小
Integer size = context.getIntAttribute("size");
// 是否只读
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
// 是
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
TransactionalCacheManager
从上面CachingExecutor的query,update,commit等操作可以看出,并没有直接操作Cache对象,而是通过TransactionalCacheManager来操作cache
TransactionalCacheManager的主要作用就是为缓存添加了事务管理,当通过putObject来添加缓存时,此时缓存并不会直接添加到底层的缓存对象中,并且通过getObject来查询这个新添加的缓存也是查不到的,只有commit了之后,才会将添加的缓存真正地添加到底层的缓存对象中
重要属性
// key是要操作的缓存对象,value是TransactionalCache
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
getObject
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
private TransactionalCache getTransactionalCache(Cache cache) {
// 如果当前cache对应的TransactionalCache不存在,就创建一个
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
putObject
public void putObject(Cache cache, CacheKey key, Object value) {
// 也是操作cache对应的TransactionalCache
getTransactionalCache(cache).putObject(key, value);
}
commit
public void commit() {
// 遍历每个Cache对应的TransactionalCache进行提交
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
TransactionalCache
从上面的代码可以看出,当通过TransactionalCacheManager来操作缓存对象时,都会将请求交给TransactionalCache
所以有关二级缓存的操作逻辑都集中在TransactionalCache
重要属性
// 实际的缓存对象
private final Cache delegate;
// 是否在commit的时候清空
private boolean clearOnCommit;
// key是缓存key,value是对应需要添加的缓存
private final Map<Object, Object> entriesToAddOnCommit;
// 没有命中缓存的key的集合
private final Set<Object> entriesMissedInCache;
getObject
public Object getObject(Object key) {
// issue #116
// 读取缓存
Object object = delegate.getObject(key);
// 没有命中缓存
if (object == null) {
// 记录没有命中缓存的key
entriesMissedInCache.add(key);
}
// issue #146
// 判断标志位,当调用clear方法后,会将这个标志位设置为true,此时即使底层的缓存对象中存在对应的缓存,仍然会返回Null
// 当调用commit方法后,会重新将这个标志位设置为false
if (clearOnCommit) {
return null;
} else {
return object;
}
}
putObject
@Override
public void putObject(Object key, Object object) {
// 这里需要注意,并没有直接操作底层的缓存对象
entriesToAddOnCommit.put(key, object);
}
commit
public void commit() {
// 判断之前是否调用了clear,如果调用了会将底层的缓存清空
if (clearOnCommit) {
delegate.clear();
}
// 这里才会真正地将通过putObject添加的缓存添加到底层的缓存对象中
flushPendingEntries();
// 将clearOnCommit设置为false,并且将entriesToAddOnCommit和entriesMissedInCache清空
reset();
}
flushPendingEntries
private void flushPendingEntries() {
// 遍历通过putObject添加的缓存
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
// 真正地添加到底层的缓存对象中
delegate.putObject(entry.getKey(), entry.getValue());
}
// 将entriesMissedInCache中不包含的缓存项添加到二级缓存中
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}