mybatis的缓存分为一级缓存和二级缓存java
一级缓存:基于SqlSession级别的缓存,也就是说,缓存了这个SqlSession执行全部的select.MapperStatement的结果集;同一个查询语句,只会请求一次;可是当前SqlSession执行增删改操做或者commit/rollback操做时,会清空SqlSession的一级缓存;sql
禁止一级缓存(同理也禁止了二级缓存)数据库
xml方式: <select id="ping" flushCache="true" resultType="string"> ... </select> 注解方式: @Options(flushCache = FlushCachePolicy.TRUE)
一级缓存致使的问题:每一个SqlSession可能会对同一个mapperStatement缓存不一样的数据,如:设计模式
这致使了sqlSession1和sqlSession2对于userId=1的缓存数据不一致,引入脏数据缓存
一级缓存源代码:安全
public abstract class BaseExecutor implements Executor { // 一级缓存 protected PerpetualCache localCache; protected BaseExecutor(Configuration configuration, Transaction transaction) { ... // 默认使用PerpetualCache this.localCache = new PerpetualCache("LocalCache"); ... } @Override public int update(MappedStatement ms, Object parameter) throws SQLException { ... // 增删改删除一级缓存 clearLocalCache(); ... } @SuppressWarnings("unchecked") @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... // flushCache=true时清空一级缓存 if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } ... // 判断一级缓存是否有值 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); } ... } @Override public boolean isCached(MappedStatement ms, CacheKey key) { // 一级缓存是否包含cachekey return localCache.getObject(key) != null; } @Override public void commit(boolean required) throws SQLException { ... // commit 删除缓存 clearLocalCache(); ... } @Override public void rollback(boolean required) throws SQLException { ... // rollback删除缓存 clearLocalCache(); ... } @Override public void clearLocalCache() { if (!closed) { // 清空缓存 localCache.clear(); localOutputParameterCache.clear(); } } private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ... try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { // 删除原来的一级缓存 localCache.removeObject(key); } // 将新获取的值放入一级缓存 localCache.putObject(key, list); 。。。 } }
二级缓存:基于SqlSessionFactory缓存,全部SqlSession查询的结果集都会共享;其实这样描述是不许确的,二级缓存是基于namespace的缓存,每一个mapper对应一个全局Mapper namespace;当第一个Sqlsession查出的结果集,缓存在namespace中,第二个sqlsession再查找时会从nameSpace中获取;每一个namespace是单例的;只有sqlSession调用了commit方法才会生效session
禁止二级缓存:mybatis
mybatis-congif.xml:将全部的namespace都关闭二级缓存 <settings> <setting name="cacheEnabled" value="false"/> ... </settings> 对单个namespace是否使用二级缓存 <cache /> 当前namespace是否使用二级缓存 <cache-ref namespace="..." /> 当前namespace和其它namespace共用缓存 对一个namespace中的单个MapperStatement关闭二级缓存 <select id="selectParentBeans" resultMap="ParentBeanResultMap" useCache="false"> select * from parent </select>
二级缓存致使的问题:当某个namespace出现多表查询时,会引发脏数据,如:app
此时再来查A表中的mapperStatement id = "xxx"时,仍是使用了A namespace中的二级缓存;这引发了B表id=5的脏数据ide
固然解决上述问题能够使用<cache-ref>;但这会致使缓存粒度变粗,多个namespace的操做都会影响该缓存;
二级缓存源码:
Configuration# public Executor newExecutor(Transaction transaction, ExecutorType executorType) { ... // 判断是否使用二级缓存 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 对executor制定插件 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
public class CachingExecutor implements Executor { // 委托设计模式 private final Executor delegate; // 二级缓存 private final TransactionalCacheManager tcm = new TransactionalCacheManager(); @Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { // 更新调用清空缓存方法(flushCache=false时不状况二级缓存) flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } @Override public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { // 判断该namespace是否含有缓存 Cache cache = ms.getCache(); if (cache != null) { // 看是否须要清空该namespace下的二级缓存 flushCacheIfRequired(ms); // 当前MapperStatement是否须要使用二级缓存 if (ms.isUseCache() && resultHandler == null) { ... list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 存入二级缓存 tcm.putObject(cache, key, list); // issue #578 and #116 ... } } } private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); // flushCache=true时清空该namespace下的二级缓存,反之则不状况缓存 if (cache != null && ms.isFlushCacheRequired()) { tcm.clear(cache); } } }
public class TransactionalCacheManager { // 存储二级缓存,以namespace的cache做为key private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>(); // 清空namespace的缓存 public void clear(Cache cache) { getTransactionalCache(cache).clear(); } // 获取某个namespace中的cacheKey的缓存 public Object getObject(Cache cache, CacheKey key) { return getTransactionalCache(cache).getObject(key); } // 放入某个namespace cacheKey的缓存 public void putObject(Cache cache, CacheKey key, Object value) { 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(); } } // transactionalCaches缓存有则不存储,没有则存入 private TransactionalCache getTransactionalCache(Cache cache) { return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new); } }
在看源代码的时候,transactionalCaches引发深思
其实二级缓存是以namespace粒度存储在Mapper里面的;每一个mapper是全局共享的;并且getTransactionalCache这个方法已经将当前的namespace存放于每一个cachingExecutor中了,因此达到了线程安全且sqlSession共享