mybatis 的一级缓存一般情况很少使用,其原因主要有两个:
所以通常都设置一级缓存的范围为:STATEMENT
,就是每次查询后会清除缓存
<setting name="localCacheScope" value="SESSION"/>
启用可以配置:
<setting name="cacheEnabled" value="true"/>
一般默认情况下是启用的
源码分析:
直接进入BaseExecutor的query方法中,具体请看注释
@SuppressWarnings("unchecked")
@Override
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."); }
// 查询的时候一般不清楚缓存,但是可以通过 xml配置或者注解强制清除,queryStack == 0 是为了防止递归调用
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();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// 一级缓存本身不能关闭,但是可以设置作用范围 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;
}
mybatis 二级缓存要稍微复杂一点,中间多了一步事务缓存:
开启是要在mapper文件中设置:
<cache eviction="FIFO" flushInterval="60000" size="2" readOnly="true"/>
缓存配置:
此外还可以配置各种二级缓存策略,比如大小,刷新间隔时间,淘汰策略等,这里主要就是使用了 Cache 接口的装饰者模式:
源码分析:
直接看CachingExecutor中的query方法:
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);
// 查询到数据,后放入缓存中,这个时候是放入到entriesToAddOnCommit中的
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
查看tcm.getObject的方法发现是从delegate中获取缓存的
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;
}
}
查看一下TransactionalCache由那些字段
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
// 实际的事务缓存数据
private final Cache delegate;
private boolean clearOnCommit;
// 是待添加的数据,需要等待事务缓存提交之后才会添加到delegate中去
private final Map<Object, Object> entriesToAddOnCommit;
private final Set<Object> entriesMissedInCache;
}
我们知道是从哪里获取二级缓存的了,那么二级缓存是在什么时候放进去的呢?
查看sqlSession.commit方法可以知道最后会调用CachingExecutor的commit方法,源码如下:
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
最后便会跑到TransactionalCache的flushPendingEntries方法,在这里会将事务缓存放进去,然后其他sqlSession才能获取的到缓存
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);
}
}
}
到此二级缓存也分析的差不多了。
mybatis 一级缓存的生命周期和 SqlSession 是一样的,通常情况下不建议使用一级缓存,通常将一级缓存范围设置为 STATEMENT;
使用 mybatis 二级的时候,务必记得 SqlSession.commit ,否则二级缓存是不生效的;
在配置 mybatis 分布式二级缓存的时候,要确保缓存淘汰等策略是可以用于分布式缓存的;