一级缓存
mybatis一级缓存流程图:
mybatis的一级缓存是SqlSession级别的缓存,在sqlSession中有一个很重要的属性:Executor对象,实际是CachingExecutor;
在DefaultSqlSessionFactory中创建SqlSession的时候,会创建executor,而executor是由configuration.newExecutor()创建的;
public class Configuration{
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 {//会创建一个simpleExecutor
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {//3.5.6版本cacheEnabled默认为true;executor是一个simpleExecutor对象;
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
}
SimpleExecutor的继承关系:SimpleExecutor -》 BaseExecutor -》 Executor
sqlSession在执行查询时的主逻辑代码:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);//获取查询sql
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);//创建当前sql的cacheKey;
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();//null,二级缓存中很重要的一个变量;只开启一级缓存,这个cache是永远为空的;
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);
}
。。。。。。
。。。。
}
执行查询的主要流程是上面的第二个query方法,在一级缓存时,Cache cache = ms.getCache();cache==null;
会直接执行最后一句:delegate.query();而delegate是一个SimpleExecutor对象;会进入SimpleExecutor中执行query方法,但是SimpleExecutor没有这个方法,所以会进入到BaseExecutor (SimpleExecutor是BaseExecutor的子类)中;
所以整个查询的主要逻辑过程,都会在baseExecutor中的query方法中;
首先会查询缓存中的数据,如果查询不到就会从数据库中查询;
public class BaseExecutor{
protected PerpetualCache localCache;//存储本地缓存;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;//configuration环境变量;
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;//获取当前sql的本地缓存数据
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {//为空,从数据库中查询;
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
。。。。
。。。
}
return list;
}
}
上面代码可以看到,本地缓存是由一个叫 localCache(PerpetualCache)对象缓存查询结果;PerpetualCache中实际存储数据的是一个叫cache的变量,本质是一个HashMap;存储的方式是:key-value键值对;key是由BaseExecutor的createCacheKey()生成,关于如何生成cacheKey就不展开了,有一篇博客cachekey生成;value是当前sql查询的结果;
总结一下:
一级缓存中主要执行查询逻辑的是在BaseExecutor的query方法中;而缓存的数据则存储在PerpetualCache中;
二级缓存
mybatis二级缓存图:
mybatis开启二级缓存:也会生成CachingExecutor,执行的流程也和一级缓存一样;但还是有细微的差别,ms.getCahe() !=null,,这样就导致进入的流程不一样;
public class CachingExecutor{
private final Executor delegate;//SimpleExecutor
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {//在创建CachingExecutor时传进来的是一个SimpleExecutor
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();//在配置了开启二级缓存(配置<cache />)之后,会创建一个cache对象;
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); // 将数据添加到entriesToAddOnCommit中,还没有添加到真正的缓存中;待会儿看详细代码;
}
return list;//缓存中获取到数据,就直接返回,不用查询数据库;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
由于cache!=null,所以会进入到if分支里面;
首先获取缓存,命中缓存就会返回数据;没有命中,走一级缓存;
这里面有一个很重要的点,为什么当前ms.getCache()能够获得其他session的缓存?ms是MappedStatement对象;
首先看看ms是如何获取到的:
public class DefaultSqlSession{
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
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();
}
}
ms是从configuration对象中获取;而confuguration是唯一的,mapper接口中的每一个方法都对应一个MappedStatement;
每一个sqlsession对象都会有confuguration对象,而这个对象是所有session共享;因此这个ms也是所有session共享的;将cache放在ms中所有的session都能获取到cache,这就是能实现二级缓存的原因;(说到这里,其实可以试一下加载2次,config.xml文件那应该会生成2个configuration,这个时候分别生成的2个sqlSession,即使开启二级缓存,2个session之间的缓存数据应该也不会共享);
回到上面代码中,我们看下tcm中如何存储数据?
tcm.putObject(cache, key, list);tcm是TransactionalCacheManager 对象,通过代码,发现最后调用的是TransactionalCache的putObject
public class TransactionalCache implements Cache {
private final Cache delegate;//缓存数据
private final Map<Object, Object> entriesToAddOnCommit;
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
//将entriesToAddOnCommit数据添加到delegate
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);
}
}
}
调用putObject方法只能把数据保存在entriesToAddOnCommit中;并没有保存到delegate中,而delegate才是二级缓存;也就是说代码执行到这里,二级缓存中是没有数据的;
那么要如何在能将数据保存到delegate呢?
实际是通过,sqlSession.commit();最终会执行到TransactionalCache 的commit()方法,而在commit中会调用flushPendingEntries(),flushPendingEntries()方法可以将entriesToAddOnCommit数据添加到delegate中;
因此开启二级缓存要生效必须要调用commit方法;