引子
上一篇文章以及讲解了mybatis自带的二级缓存是如何被创建的,现在我们就来看看二级缓存的是如何使用的
其实在前面的介绍二级缓存的时候我说过,二级缓存是利用一级缓存的数据在SqlSession调用commit或者close时导入到二级缓存的,那么如果在利用一个SqlSession实现了更新等操作时便会刷新一级缓存从而导致在提交了事务或关闭时,一级缓存传入到二级缓存的数据是空的。从结果来说其实并没有错,但是如果我们调用sqlSession的clearcache方法时我们就会发现很有趣的问题:
一级缓存的确没了但是提交关闭事务后二级缓存仍然有数据。大家可以自己尝试一下.这个结果其实告诉了我们
- clearcache方法可以刷新一级缓存,但是二级缓存仍然可以得到数据
- 存放一级缓存的对象与将存放的缓存提交到二级缓存的对象并不是同一个对象
这其实就涉及到了二级缓存在使用上的机制问题,既然要解释这个问题就要看看SqlSession到底是如何运作的,其实
sqlSession的大部分操作都是依靠executor对象来执行的,简单的看一下基本逻辑:
当然实际上execut中会装饰几次,所以并不像图片那么简单,我们先看看创建一个sqlSession的方式:
当调用SqlSessionFactory的openSession方法时实我们最终会使用这个方法
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();
}
}
我们主要关注的便是configuration.newExecutor(tx, execType);这个方法便会为我们创建一个执行器,
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//默认的执行器类型是SimpleExecutor
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);
}
//如果声明了二级缓存便会对执行器进行包装形成CachingExecutor
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
这里就比较关键了如果没有开启二级缓存那么执行器便是默认的SimpleExecutor在这里面保存的便是一级缓存,如果开启了二级缓存,便会对执行器进行包装(CachingExecutor中便保存还未提交的二级缓存数据),这里再把Executor的图细化便是这样:
OK,我们大致看到了创建sqlSession执行器的过程,现在我们就来看看二级缓存的读取与保存方式:
二级缓存的读取与保存
在利用代理接口时最终我们知道会调用Sqlsession中的方法,这里由于涉及缓存我们便直接看查询的方法:
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
//执行selectList方法
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
可以看到我们实际上执行的是selectList方法来看看:
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//获取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里就很关键了,我们通过statement(也就是调用的sql的id)找到了对应的MappedStatement(上一篇文章也说了这里持有了自己mapper的缓存引用)
最后我们便会执行executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);而这个便是执行器Cachingexecutor:
@Override
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);
}
@Override
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, parameterObject, boundSql);
@SuppressWarnings("unchecked")
//这个方法会去二级缓存中寻找
List<E> list = (List<E>) tcm.getObject(cache, key);
//当二级缓存中没有这个数据时便会利用被包装的执行器,这里是SimpleExecutor
if (list == null) {
//利用SimpleExecutor,如果里面的一级缓存没有的话便会到数据库中查找
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//将查找的结果保存的临时的缓存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
List list = (List) tcm.getObject(cache, key);
tcm是CachingExecutor中的TransactionalCacheManager对象而这个对象也是最重要的:
public class TransactionalCacheManager {
private final 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();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
TransactionalCache txCache = transactionalCaches.get(cache);
if (txCache == null) {
txCache = new TransactionalCache(cache);
transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
TransactionalCacheManager对象会为每个要使用的先Cache创建一个TransactionalCache对象(实现了cache接口),而这个对象便会对传入的Cache进行再一次的包装,而这次包装的目的便是保存临时的二级缓存:
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
private boolean clearOnCommit;
private final Map<Object, Object> entriesToAddOnCommit;
private final Set<Object> entriesMissedInCache;
//对传入的cache再一次包装
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<Object, Object>();
this.entriesMissedInCache = new HashSet<Object>();
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
public void rollback() {
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
//这个方法便会将临时的二级缓存放到perpetual当中
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifiying a rollback to the cache adapter."
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
这里主要要注意 entriesToAddOnCommit对象,这个对象便是实际保存了临时的二级缓存,当我们commit或者close时这里的临时缓存便会最终放到perpetualCache当中.
回到上面我们调用List list = (List) tcm.getObject(cache, key);
public Object getObject(Cache cache, CacheKey key) {
return getTransactionalCache(cache).getObject(key);
}
首先得到这个mapper的cache对应的transactionCache,然后依次层层获取最终到perpetualCache查找数据,没有便会返回空值,回到我们的query方法:
@Override
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, parameterObject, boundSql);
@SuppressWarnings("unchecked")
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 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
当二级缓存中没有这个数据时,CachingExecutor便会调用SimpleExecutor,这里便是一级缓存了。经过这个方法我们得到了数据(可能再一级缓存中也可能直接连接数据库中),然后CachingExecutor便会将这个数据根据mapper的cache放到对应transactionCache中的 entriesToAddOnCommit对象中。这里就可以看出transactionCache实现cache接口的putObject方法并不会按照责任链依次调用putObject放入mapper的cache中,而是暂时保存在transactionCache里面,一旦session有更新等操作时便会刷新entriesToAddOnCommit对象
最后回到最前面的问题,我们已经知道了CachingExecutor是包装了CachingExecutor,因此在执行更新等操作时CachingExecutor便会清除entriesToAddOnCommit中的数据,同时CachingExecutor也会清除一级缓存,但是session的clearCache清除的是一级缓存(本地缓存)并不会清除entriesToAddOnCommit的缓存,我们可以从CachingExecutor的实现方法看到:
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
@Override
public void clearLocalCache() {
delegate.clearLocalCache();
}
CachingExecutor只会调用被装饰的执行器的clearLocalCache方法而不会清除临时的二级缓存
最后当SqlSession执行commit或者close时都会执行transactionCache的flushPendingEntries()方法这个方法中便会将entriesToAddOnCommit中的数据提交到mapper的cache当中:
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}