本文中有些内容与之前关于一级缓存分析的文章中,有部分内容相似,所以有些会直接跳过,建议可以先看看关于一级缓存分析的文章一级缓存源码分析
二级缓存生命周期
mybatis一级缓存生命周期是sqlSession级别,二级缓存生命周期是sqlSessionFactory级别,也就是整个应用级别的生命周期。
如何开启二级缓存
mybatis二级缓存与一级缓存不同,需要先在全局配置文件中配置,默认开启
<setting name="cacheEnabled" value="true" />
还需要在mapper文件中添加如下标签属性
<cache></cache>
二级缓存失效策略
1、二级缓存失效同一级缓存一样,增删改都会造成缓存清除
2、默认情况不是同一个namespace也不会走同一个缓存
3、自身属性配置的刷新时间和淘汰策略。
二级缓存调用过程解析
之前在介绍一级缓存时解析过,当mybaits开启sqlsession时,会先初始化一些关键信息,看下与缓存相关的。
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);
}
//如果在全局配置文件中配置的true,则通过装饰器模式把Executor包装成CachingExecutor类型
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
mybatis使用了装饰者模式开启二级缓存的能力。
通过之前一级缓存的文章中,我们得知当开启了二级缓存后,我们再查询的时候,这时候的Executor实现类就是CachingExecutor了。
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
@Override
public Transaction getTransaction() {
return delegate.getTransaction();
}
@Override
public void close(boolean forceRollback) {
try {
//issues #499, #524 and #573
if (forceRollback) {
tcm.rollback();
} else {
tcm.commit();
}
} finally {
delegate.close(forceRollback);
}
}
@Override
public boolean isClosed() {
return delegate.isClosed();
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
//二级中key的组成与一级缓存中一样
//命名空间,方法名,分页参数,sql语句以及各种查询参数
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
flushCacheIfRequired(ms);
return delegate.queryCursor(ms, parameter, rowBounds);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//从MappedStatement中获取二级缓存
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) {
//如果获取不到,则会调用BaseExecutor的query方法
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//放入TransactionalCacheManager中,实际上是放入TransactionalCache这个对象的一个map容器中,然后在sqlsession提交或者close的时候更新二级缓存
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public List<BatchResult> flushStatements() throws SQLException {
return delegate.flushStatements();
}
@Override
public void commit(boolean required) throws SQLException {
delegate.commit(required);
tcm.commit();
}
@Override
public void rollback(boolean required) throws SQLException {
try {
delegate.rollback(required);
} finally {
if (required) {
tcm.rollback();
}
}
}
private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
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.");
}
}
}
}
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
@Override
public boolean isCached(MappedStatement ms, CacheKey key) {
return delegate.isCached(ms, key);
}
@Override
public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
delegate.deferLoad(ms, resultObject, property, key, targetType);
}
@Override
public void clearLocalCache() {
delegate.clearLocalCache();
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
@Override
public void setExecutorWrapper(Executor executor) {
throw new UnsupportedOperationException("This method should not be called");
}
}
通过源码我们可以看出,二级缓存的查询逻辑大概流程就是先从二级缓存中获取,如果获取不到则调用BaseExecutor的query方法,如果开启了一级缓存,就再从一级缓存查询,否则从数据库查询数据,然后将数据再放入二级缓存,但是二级缓存并不是直接进行更新,而是先放到了TransactionalCache对象的map容器中,等待sqlsession提交或者close再更新到二级缓存。
看一下二级缓存更新过程
public class TransactionalCacheManager {
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
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) {
//把缓存put到TransactionalCache的map容器中
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) {
return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
}
看一下TransactionalCache中的部分源码
public class TransactionalCache implements Cache {
private final Cache delegate;
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
//添加到缓存对象中,最终缓存对象也是使用个map来存储
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
}
二级缓存的构建过程解析
二级缓存在mapper配置文件中是可以设置很多相关属性的,比如过期时间,淘汰策略,容量等,那么我们通过源码看一下mybaits是如何构建二级缓存的。
<cache eviction="LRU" flushInterval="1000" size="1024"></cache>
源码分析
首先在mybaits初始化,会解析mapper文件,并解析文件中cache标签。
private void configurationElement(XNode context) {
try {
//获取namespace
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//解析cache-ref节点
cacheRefElement(context.evalNode("cache-ref"));
//解析cache节点
cacheElement(context.evalNode("cache"));
//解析parameterMap节点
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//解析resultMap节点
resultMapElements(context.evalNodes("/mapper/resultMap"));
//解析sql节点
sqlElement(context.evalNodes("/mapper/sql"));
//解析select、insert、update、delete节点
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
看一下cache节点时如何解析的
private void cacheElement(XNode context) throws Exception {
if (context != null) {
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);
}
}
builderAssistant.useNewCache方法
public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
//使用建造者模式创建缓存对象
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
//注意二级缓存以currentNamespace划分,也就是不同的namespace,不走同一个缓存,可以使用cache-ref标签应用其他的namespace。
configuration.addCache(cache);
currentCache = cache;
return cache;
}
建造者中的build方法
public Cache build() {
//设置缓存默认实现类为PerpetualCache
setDefaultImplementations();
Cache cache = newBaseCacheInstance(implementation, id);
setCacheProperties(cache);
if (PerpetualCache.class.equals(cache.getClass())) {
for (Class<? extends Cache> decorator : decorators) {
cache = newCacheDecoratorInstance(decorator, cache);
setCacheProperties(cache);
}
//根据标签中设置的cache属性添加相应的功能
cache = setStandardDecorators(cache);
} else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
//包装日志功能
cache = new LoggingCache(cache);
}
return cache;
}
setStandardDecorators方法
//这个方法很明显可以看出这是一个装饰者模式的运用,根据标签中设置的属性为缓存动态的添加相应功能
private Cache setStandardDecorators(Cache cache) {
try {
MetaObject metaCache = SystemMetaObject.forObject(cache);
if (size != null && metaCache.hasSetter("size")) {
metaCache.setValue("size", size);
}
if (clearInterval != null) {
cache = new ScheduledCache(cache);
((ScheduledCache) cache).setClearInterval(clearInterval);
}
if (readWrite) {
cache = new SerializedCache(cache);
}
cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
if (blocking) {
cache = new BlockingCache(cache);
}
return cache;
} catch (Exception e) {
throw new CacheException("Error building standard cache decorators. Cause: " + e, e);
}
}
通过上面一系列的源码我们可以看出,二级缓存的主要构建过程就是在初始化时先获取mapper文件中cache标签,并得到标签中设置的属性,然后通过建造者模式构建缓存对象,在具体的构建过程中又是通过装饰者模式实现根据不同的属性组合,优雅的为二级缓存添加相应属性功能。
通过分析mybaits源码,大家可以从中学习到许多经典的设计模式,看看在mybatis中是如何把这些设计模式运用到实际的项目中去的。