赞
踩
视频地址:https://www.bilibili.com/video/BV1nP411A7Gu
MyBatis的缓存是一个常见的面试题
注:这个是基于源码的角度来讲解缓存,如果对MyBatis源码不熟悉的小伙伴建议先看看这个 MyBatis 执行原理,源码解读
在写这篇博客的时候突然想到了一个简单的问题,如果你可以回答出来,那么MyBatis的一、二级缓存你将吃透了
两次获取的结果是否一致呢?
mapper.getId()
// 打断点 ,然后去修改数据库的数据
mapper.getId()
前置的内容在上一章执行原理里面都已经讲过了,这里只提出一些个关键点,帮助理解。
MappedStatementMapperProxy 代理对象,当我们执行某个方法的时候就会调用代理对象的 invoke 方法在 MybatisAutoConfiguration 自动注入的时候会创建 SqlSessionFactory 和 SqlSessionTemplate
SqlSessionFactory的最终实现类是 DefaultSqlSessionFactory
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
创建sqlSessionTemplate的最终方法如下,它其实也是代理实现的
它在这里加了一个拦截器,这个很重要下面说 new SqlSessionInterceptor()
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
MapperProxy是基于 SqlSession来创建的,通过上面的自动注入我们得知这里的 SqlSession是 SqlSessionTemplate
这个SqlSessionTemplate 只是用来创建代理时候的一个模板,真正去执行的SqlSession 是DefaultSqlSession
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
sqlSession 就是创建我们代理对象的 SqlSessionTemplate
excute 就是去执行我们的增删改查逻辑,简化代码
public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } case SELECT: Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } return result; }
我们的 SqlSessionTemplate 创建的时候,加入了这个拦截器,所以正式去执行方法的时候是会先执行这个拦截器
简化后的SqlSessionInterceptor
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { Object result = method.invoke(sqlSession, args); if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) { // force commit even on non-dirty sessions because some databases require // a commit/rollback before calling close() sqlSession.commit(true); } return result; } catch (Throwable t) { Throwable unwrapped = unwrapThrowable(t); throw unwrapped; } finally { if (sqlSession != null) { // 【关闭session】 closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
注: MyBatis的一级缓存是 session,其实就是这个SqlSession,不要和你之前理解的cookie、session 中的session混为一谈了
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); // TransactionSynchronizationManager 是事物管理器 // 这里你可以理解成当前是否开启事物,如果开启了就会把session注册进去,从而保证这个线程获取的是同一个session 【这个holder是从 ThreadLocal 里面获取的】 SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } LOGGER.debug(() -> "Creating a new SqlSession"); // 创建新的session session = sessionFactory.openSession(executorType); // 把holder 注册到TransactionSynchronizationManager // TransactionSynchronizationManager 是事物管理器,【如果你没开启事物那就不会注册成功的】 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
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();
}
}
它这里创建什么类型的Executor,我们不用关注,我们的目的是缓存
我们可以看到当 CachingExecutor == true 的时候,会把executor装饰成 CachingExecutor ,CachingExecutor 就是二级缓存了 (ps:这里使用的是装饰器模式)
<cache></cache>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); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
获取到真正的 sqlSession 之后,就会去执行我们的查询,这selectList 有很多的重载
@Override public <T> T selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List<T> list = this.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; } } @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) { return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER); }
最终的调用如下
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
executor 的创建看上面的 newExecutor 方法,我们知道最终创建的是 CachingExecutor
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 对sql进行解析 参看上一篇博客
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 生成缓存key 参看上一篇博客
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 { // 从MappedStatement 里面获取缓存二级缓存,【这也就是为什么说 二级缓存是基于Mapper的】 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); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } // 刚刚我们也看到创建的CachingExecutor 是基于装饰器模式创建的,里面真正的是 SimpleExecutor // SimpleExecutor extends BaseExecutor 所以这里的query 方法是BaseExecutor的 return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
@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."); } // queryStack 这个字段暂且不看,它默认就是0 // isFlushCacheRequired 这字段表示是否要清空缓存, // 1.它是从ms里面获取的 // 2.虽然我们的一级缓存不能被关闭,但是可以让它失效,查缓存之前删除缓存就好了 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; // resultHandler 上面就是传递的null,所以这里一定会走缓存 // 因为这句代码一定会走,这就是【一级缓存不能关闭的原因】 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 级别就清空缓存【默认是session级别】 if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; }
我们再来基于上面的 BaseExecuto#query 来看看一级缓存
默认就开启的,无法关闭,因为一定会走这段代码,但是我们可以让缓存失效,失效有两种方式 修改缓存级别为 STATEMMENT (默认是 session) 和 开启 isFlushCacheRequired
缓存级别可以通过配置文件来配置
mybatis:
configuration:
local-cache-scope: statement
isFlushCacheRequired 是MappedStatement的一个配置属性,首先会获取 insert/update/delete/select 标签中的 flushCache 值,如果没有配置,那如果是select 就默认 true,其它就是false
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
这个session,是 sqlSession,而不是cookie、session中的session
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
这句代码是在 Executor 里面执行的,所以这个 localCache 是Executor的,而Executor又被放入了 sqlSession
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();
}
}
这个问题是不是很奇怪?以前我们知道一级缓存不会被关闭,理所当然的以为每次都走一级缓存了,现在我们知道其实有办法让一级缓存失效。那不是理所当然的只要没失效就会走吗?
事实证明不是这样的,我们再来回顾一下 : 一级缓存是基于 sqlSession的,如果两次查询用的不是一个 sqlSession呢?
问题变得明朗了:这个 sqlSession的生命周期是什么?
下面是 SqlSessionInterceptor 简化后的代码,我们可以看到不管怎么样最后一定会关闭sqlSession
private class SqlSessionInterceptor implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 获取 sqlSession SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory, SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator); try { // 执行方法 Object result = method.invoke(sqlSession, args); return result; } catch (Throwable t) { // 异常处理..... } finally { // 【关闭 sqlSession】 if (sqlSession != null) { closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory); } } } }
如果每个方法在执行结束后都关闭了 sqlSession,那不是完蛋了?每次都是新的 sqlSession,那还缓存个锤子?
当然事情肯定不是这样的,我们再来仔细看看 获取和关闭sqlSession的具体逻辑
getSqlSession
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) { notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED); // 如果当前我们开启了事物,那就从 ThreadLocal 里面获取 session SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); SqlSession session = sessionHolder(executorType, holder); if (session != null) { return session; } LOGGER.debug(() -> "Creating a new SqlSession"); // 没有获取到 session,创建一个 session session = sessionFactory.openSession(executorType); // 如果当前开启了事物,就把这个session注册到当前线程的 ThreadLocal 里面去 registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session); return session; }
closeSqlSession

public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) { notNull(session, NO_SQL_SESSION_SPECIFIED); notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED); SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); if ((holder != null) && (holder.getSqlSession() == session)) { LOGGER.debug(() -> "Releasing transactional SqlSession [" + session + "]"); holder.released(); } else { LOGGER.debug(() -> "Closing non transactional SqlSession [" + session + "]"); session.close(); } } public void released() { --this.referenceCount; }
现在真相大白了,如果我们方法是开启事物的,则当前事物内是获取的同一个 sqlSession,否则每次都是不同的 sqlSession
早期背面试题的时候,我连这个都不会读,后面会读了但依旧不知道是个什么东西, 其实它就是一个 HashMap ,只是给这个HashMap命名为 PerpetualCache
protected PerpetualCache localCache; // 重写的 equals 和 hashCode 我就省略了 public class PerpetualCache implements Cache { private final String id; private final Map<Object, Object> cache = new HashMap<>(); public PerpetualCache(String id) { this.id = id; } @Override public String getId() { return id; } @Override public int getSize() { return cache.size(); } @Override public void putObject(Object key, Object value) { cache.put(key, value); } @Override public Object getObject(Object key) { return cache.get(key); } @Override public Object removeObject(Object key) { return cache.remove(key); } @Override public void clear() { cache.clear(); }
其实insert和delete,底层都是调用的update
@Override public int insert(String statement, Object parameter) { return update(statement, parameter); } @Override public int update(String statement) { return update(statement, null); } @Override public int update(String statement, Object parameter) { try { dirty = true; MappedStatement ms = configuration.getMappedStatement(statement); return executor.update(ms, wrapCollection(parameter)); } catch (Exception e) { throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e); } finally { ErrorContext.instance().reset(); } } @Override public int delete(String statement, Object parameter) { return update(statement, parameter); }
之前我们也说了,最终都是装载的 CachingExecutor
CachingExecutor#update 第一步就是删除二级缓存
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
Cache cache = ms.getCache();
// 前面也讲过了对于非 select isFlushCacheRequired 默认就是 true
// boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
if (cache != null && ms.isFlushCacheRequired()) {
tcm.clear(cache);
}
}
delegate.update(ms, parameterObject); 去调用我们的 BaseExecutor,也是先清空缓存
@Override 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); } @Override public void clearLocalCache() { if (!closed) { localCache.clear(); localOutputParameterCache.clear(); } }
理解了一级缓存,我们再来看二级缓存其实也很简单,还是以问题的方式来解答
二级缓存不像一级缓存默认就是开启的,我们需要在要开启二级缓存Mapper里面加上 cache标签,加了这个标签就开启了

通过上面的代码,我们知道在执行二级缓存之前,先会去 MappedStatement 里面获取 cache,如果 cache不为空,我们就用二级缓存,再来看看下面的源码
@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, 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); }
完整的 Mapper解析参看上一篇文章,这里我们只看 cache 标签解析
private void configurationElement(XNode context) {
try {
// ....
cacheElement(context.evalNode("cache"));
// ....
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
private void cacheElement(XNode context) { 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); } } 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(); configuration.addCache(cache); // 属于 builderAssistant 类,创建 MappedStatement 的时候就会把这个 currentCache 设置到 cache属性里面 currentCache = cache; return cache; }
二级缓存是存在 MappedStatement 里面的,而MappedStatement 是由一个个 select、insert、update、delete 标签解析来的,所以就说二级缓存的作用域是 Mapper
在 CachingExecutor 里面的 query 方法很容易就看到了,如果开启二级缓存那就用二级,不然就是一级

上面我们只是讲解了最基础的二级缓存,其实cache标签还有很多参数
<cache type="" eviction="" readOnly="" flushInterval="" size="" blocking=""></cache>
解析 cache 标签时候的代码 cacheElement
private void cacheElement(XNode context) { if (context != null) { // Type 缓存的类型,如果没有默认就是 PerpetualCache String type = context.getStringAttribute("type", "PERPETUAL"); Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); // eviction 缓存策略,默认是 LRU 这个下面解释 【这里用到了装饰器模式哦】 String eviction = context.getStringAttribute("eviction", "LRU"); Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); // 如果你设置了这个缓存刷新时间,就会间隔这个时间去清空缓存(包装成 ScheduledCache) Long flushInterval = context.getLongAttribute("flushInterval"); // 缓存的大小 Integer size = context.getIntAttribute("size"); // 是否只读 boolean readWrite = !context.getBooleanAttribute("readOnly", false); // 是不是阻塞缓存,是的话会包装成 BlockingCache boolean blocking = context.getBooleanAttribute("blocking", false); // 获取其它自定义标签内容 Properties props = context.getChildrenAsProperties(); // 获取参数完毕,组装缓存 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
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(); configuration.addCache(cache); currentCache = cache; return cache; }
我们来看看这个 build 建造者模式
public Cache build() { // 设置默认的缓存实现类和默认的装饰器(PerpetualCache 和 LruCache) setDefaultImplementations(); // 创建基本的缓存 Cache cache = newBaseCacheInstance(implementation, id); // 设置自定义的参数 setCacheProperties(cache); // 如果是PerpetualCache 的缓存,我们将进一步处理 if (PerpetualCache.class.equals(cache.getClass())) { for (Class<? extends Cache> decorator : decorators) { // 进行最基本的装饰 cache = newCacheDecoratorInstance(decorator, cache); // 设置自定义的参数 setCacheProperties(cache); } // 创建标准的缓存,也就是根据配置来进行不同的装饰 cache = setStandardDecorators(cache); } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) { // 如果是自定义的缓存实现,这里只进行日志装饰器 cache = new LoggingCache(cache); } return cache; }
通过上面的缓存实现,我们发现了很多缓存的装饰器,什么 LruCache、ScheduledCache、LoggingCache 我们来看看到底有多少种装饰器

我们通过装饰器的名字就大概猜到了要干什么。
注:这里说一下我最开始以为它们都是cache的实现类,其实不是,真正的实现类只有 PerpetualCache ,红框框里面的都是对 PerpetualCache 的包装。
了解了缓存装饰器,我们接着看 setStandardDecorators
private Cache setStandardDecorators(Cache cache) { try { // 获取当前 cache的参数 MetaObject metaCache = SystemMetaObject.forObject(cache); // 如果设置了 size 就设置size的大小 if (size != null && metaCache.hasSetter("size")) { metaCache.setValue("size", size); } // 如果设置了缓存刷新时间,就进行ScheduledCache 装饰 if (clearInterval != null) { cache = new ScheduledCache(cache); ((ScheduledCache) cache).setClearInterval(clearInterval); } // 如果缓存可读可写,就需要进行序列化 默认就是 true,这也是为什么我们的二级缓存的需要实现序列化 if (readWrite) { cache = new SerializedCache(cache); } // 默认都装饰 日志和同步 cache = new LoggingCache(cache); cache = new SynchronizedCache(cache); // 如果开启了阻塞就装配阻塞 if (blocking) { cache = new BlockingCache(cache); } // 返回层层套娃的cache return cache; } catch (Exception e) { throw new CacheException("Error building standard cache decorators. Cause: " + e, e); } }
这里的装饰器模式真实妙哇,看完之后大呼过瘾,随便怎么组合进行装饰,是理解装饰器模式的好案例。
通过上面的源码,我们知道默认就是可读可写的缓存,它会用 SynchronizedCache 进行装饰,我们来看来SynchronizedCache 的putObject 方法,你就知道了
@Override
public void putObject(Object key, Object object) {
if (object == null || object instanceof Serializable) {
delegate.putObject(key, serialize((Serializable) object));
} else {
throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
}
}
当然了如果你把设置成只读的,就没问题了,但这样又失去了它存在的意义。
通过上面我们知道所有的缓存都是基于Mybatis的基础之上的,如果你直接对数据操作,那Mybatis是无法感知的,而在分布式的场景下,其实就相当于直接修改数据库了。
一级缓存: 一级缓存是 sqlSession,而只要没开启事物哪怕一个线程每次获取的也都是新的 sqlSession,相当于没有缓存。而对于一个事务而言,我们就是要保证它多次读取的数据是一致的。
二级缓存:二级缓存默认就是不开启的,在分布式的情况下也一定不要开启二级缓存。
因为MappedStatement 不存在删除的情况,所以二级缓存失效只有两种情况
熟悉了上面的缓存原理,我们再来看这个问题就简单了
mapper.getId()
// 打断点 ,然后去修改数据库的数据
mapper.getId()
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。