首先了解下这个设计的目的:
注意二级缓存是从 MappedStatement 中获取的。由于 MappedStatement 存在于全局配置中,可以多个 CachingExecutor 获取到,这样就会出现线程安全问题。
例如:
会话一与会话二原本是两条隔离的事务,但由于二级缓存的存在导致彼此可见会发生脏读。若会话二的修改直接填充到二级缓存,会话一查询时缓存中存在即直接返回数据,此时会话二回滚会话一读到的数据就是脏数据。为了解决这一问题mybatis二级缓存机制就引入了事务管理器(暂存区),所有变动的数据都会暂时存放到事务管理器的暂存区中,只有执行提交动作后才会真正的将数据从暂存区中填充到二级缓存中。
(补充:同一事务下,先修改后查询,就算修改事务未提交,查询到的值也是修改后的值;如:余额开始为0元一个事务先给余额增加20元,再查询余额为20元,再在查询基础上扣除10元结果应为10元,再次事务未提交之前,其他事务查询余额,余额还为0元,只有本事务可以查询到事务未提交之前的增删改查结果)
1.进入CachingExecutor中的query方法。
我们发现二级缓存获取的真正对象来自SqlSession中的CachingExecutor中的TransactionalCacheManager类并不是MappedStatement中的二级缓存对象。
// BaseExecutor
private final Executor delegate;
//事务缓存管理器
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// 从 MappedStatement 中获取 Cache,注意这里的 Cache 是从MappedStatement中获取的 // 也就是我们上面解析Mapper中<cache/>标签中创建的,它保存在Configration中 // 我们在上面解析blog.xml时分析过每一个MappedStatement都有一个Cache对象,就是这里
Cache cache = ms.getCache();
// 如果配置文件中没有配置 <cache>,则 cache 为空
if (cache != null) {
//如果需要刷新缓存的话就刷新:flushCache="true"
this.flushCacheIfRequired(ms);
// 当前查询标签UseCache属性为true且无指定自定义resultHandler结果处理器时才走二级缓存.
if (ms.isUseCache() && resultHandler == null) {
this.ensureNoOutParams(ms, parameterObject, boundSql);
// 访问二级缓存
List<E> list = (List)this.tcm.getObject(cache, key);
// 缓存未命中
if (list == null) {
// 如果没有值,则执行查询走BaseExecutor执行器,这个查询实际也是先走一级缓存查询,一级缓存也没有的 话,则进行DB查询
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 缓存查询结果
this.tcm.putObject(cache, key, list);
}
return list;
}
}
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
2.进入TransactionalCacheManager类的getObject()方法一探究竟
TransactionalCacheManager 内部维护了 Cache 实例与 TransactionalCache 实例间的映射关系,该类也仅负责维护两者的映射关系,真正做事的还是 TransactionalCache。TransactionalCache 是一种缓存装饰器,可以为 Cache 实例增加事务功能。我在之前提到的脏读问题以及回归问题正是由该类进行处理的。
public class TransactionalCacheManager {
// Cache(二级缓存) 与 TransactionalCache 的映射关系表
// 一个SqlSession可以涉及多个Mapper即可以涉及多个二级缓存
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap();
public TransactionalCacheManager() {
}
public void clear(Cache cache) {
// 获取 TransactionalCache 对象,并调用该对象的 clear 方法,下同
this.getTransactionalCache(cache).clear();
}
public Object getObject(Cache cache, CacheKey key) {
// 直接从TransactionalCache中获取缓存
return this.getTransactionalCache(cache).getObject(key);
}
public void putObject(Cache cache, CacheKey key, Object value) {
// 直接存入TransactionalCache的缓存中
this.getTransactionalCache(cache).putObject(key, value);
}
public void commit() {
Iterator var1 = this.transactionalCaches.values().iterator();
while(var1.hasNext()) {
TransactionalCache txCache = (TransactionalCache)var1.next();
txCache.commit();
}
}
public void rollback() {
Iterator var1 = this.transactionalCaches.values().iterator();
while(var1.hasNext()) {
TransactionalCache txCache = (TransactionalCache)var1.next();
txCache.rollback();
}
}
private TransactionalCache getTransactionalCache(Cache cache) {
// 从映射表中获取 TransactionalCache
TransactionalCache txCache = (TransactionalCache)this.transactionalCaches.get(cache);
if (txCache == null) {
// TransactionalCache 也是一种装饰类,为 Cache 增加事务功能
// 创建一个新的TransactionalCache,并将真正的Cache对象存进去
txCache = new TransactionalCache(cache);
this.transactionalCaches.put(cache, txCache);
}
return txCache;
}
}
3.进入TransactionalCache一探究竟
在事务提交之前执行增删改查都是缓存到entriesToAddOnCommit集合中,只有执行SqlSession关闭或者事务提交时才会触发TransactionalCache的commit()方法,如果有执行增删改操作,则清空缓存,并将本事务中的entriesToAddOnCommit刷入二级缓存中,如果未执行增删改则直接将entriesToAddOnCommit输入二级缓存。
ublic class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
//真正的缓存对象,和上面的Map<Cache, TransactionalCache>中的Cache是同一个
private final Cache delegate;
private boolean clearOnCommit;
// 在事务被提交前,所有从数据库中查询的结果将缓存在此集合中
private final Map<Object, Object> entriesToAddOnCommit;
// 在事务被提交前,当缓存未命中时,CacheKey 将会被存储在此集合中
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap();
this.entriesMissedInCache = new HashSet();
}
public String getId() {
return this.delegate.getId();
}
public int getSize() {
return this.delegate.getSize();
}
public Object getObject(Object key) {
// 查询的时候是直接从delegate中去查询的,也就是从真正的缓存对象中查询
Object object = this.delegate.getObject(key);
if (object == null) {
// 缓存未命中,则将 key 存入到 entriesMissedInCache 中
this.entriesMissedInCache.add(key);
}
return this.clearOnCommit ? null : object;
}
public ReadWriteLock getReadWriteLock() {
return null;
}
public void putObject(Object key, Object object) {
// 将键值对存入到 entriesToAddOnCommit 这个Map中中,而非真实的缓存对象 delegate 中
this.entriesToAddOnCommit.put(key, object);
}
public Object removeObject(Object key) {
return null;
}
public void clear() {
this.clearOnCommit = true;
// 清空 entriesToAddOnCommit,但不清空 delegate 缓存
this.entriesToAddOnCommit.clear();
}
public void commit() {
// 根据 clearOnCommit 的值决定是否清空 delegate
if (this.clearOnCommit) {
this.delegate.clear();
}
// 刷新未缓存的结果到 delegate 缓存中
this.flushPendingEntries();
// 重置 entriesToAddOnCommit 和 entriesMissedInCache
this.reset();
}
public void rollback() {
this.unlockMissedEntries();
this.reset();
}
private void reset() {
this.clearOnCommit = false;
// 清空集合
this.entriesToAddOnCommit.clear();
this.entriesMissedInCache.clear();
}
private void flushPendingEntries() {
Iterator var1 = this.entriesToAddOnCommit.entrySet().iterator();
while(var1.hasNext()) {
Entry<Object, Object> entry = (Entry)var1.next();
// 将 entriesToAddOnCommit 中的内容转存到 delegate 中
this.delegate.putObject(entry.getKey(), entry.getValue());
}
var1 = this.entriesMissedInCache.iterator();
while(var1.hasNext()) {
Object entry = var1.next();
if (!this.entriesToAddOnCommit.containsKey(entry)) {
// 存入空值
this.delegate.putObject(entry, (Object)null);
}
}
}
private void unlockMissedEntries() {
Iterator var1 = this.entriesMissedInCache.iterator();
while(var1.hasNext()) {
Object entry = var1.next();
try {
// 调用 removeObject 进行解锁
this.delegate.removeObject(entry);
} catch (Exception var4) {
log.warn("Unexpected exception while notifiying a rollback to the cache adapter.Consider upgrading your cache adapter to the latest version. Cause: " + var4);
}
}
}
}
总结:
二级缓存增删改事务提交后会清空二级缓存,查操作只有在事务提交或关闭后才会刷如二级缓存