探秘MyBatis缓存原理:Cache接口与实现类源码分析
缓存
缓存即将数据存储在内存上,传统的数据库,大量的数据都会存储在硬盘之上,而硬盘的读写效率是大大低于内存的,所以缓存的价值是在程序和数据库之间搭建一个桥梁,将一部分数据存储在内存,提高用户的查询效率。这是一种典型的空间换时间的优化策略。接下来我们将会进入 MyBatis 源码,学习它的来龙去脉。
Cache 接口
MyBatis 的 Cache 接口是用于提供数据缓存功能的接口,它允许 MyBatis 将查询结果缓存起来,以提高查询性能。这个接口定义了缓存的基本行为和操作方法,同时也提供了默认的实现。通过观察 Cache 接口,我们不难发现 Cache 是基于键值对的结构进行设计的。
public interface Cache {
/**
* 返回缓存的唯一标识,一个系统中可以设置多个缓存
*/
String getId();
/**
* 将查询结果放入缓存中。其中,key 通常是一个唯一标识符,可以是 SQL 语句的 ID 或者是参数对象的哈希值,value 则是查询结果对象。
*/
void putObject(Object key, Object value);
/**
* 从缓存中获取指定 key 对应的查询结果对象。
*/
Object getObject(Object key);
/**
* 从缓存中移除指定 key 对应的查询结果对象。
*/
Object removeObject(Object key);
/**
* 清空缓存,移除所有的缓存对象。
*/
void clear();
/**
* 获取缓存的数据条数。
*/
int getSize();
/**
* 获取读写锁,但是后面 MyBatis 已将此方法废弃。
*/
ReadWriteLock getReadWriteLock();
}
Cache 实现类
MyBatis 提供了几种默认的缓存实现类,每种实现类都有其特定的特点和适用场景。以下是 MyBatis 中常见的缓存实现类:
可以看到,除了 PerpetualCache 是 ibatis.cache.impl 包,其他都是位于 decorators 包下,表示这些都是装饰器,用来丰富 PerpetualCache 的核心功能。
PerpetualCache
PerpetualCache
是 MyBatis 中的一个简单缓存实现类,它是其他缓存实现的基础。PerpetualCache
是一个基于内存的缓存,它使用一个 HashMap 来存储缓存数据。与其他缓存实现不同的是,PerpetualCache
不会自动清理缓存数据,缓存中的数据会一直存在直到缓存对象被销毁。
PerpetualCache
的实现相对简单,它提供了基本的存储和获取缓存数据的功能,但不包含缓存的过期策略或者淘汰算法。因此,PerpetualCache
适用于数据量较小,但是频繁被访问的场景,或者是在不需要缓存过期策略和淘汰算法的情况下使用。
public class PerpetualCache implements Cache {
private final String id;
// 通过这个 HashMap 存储数据
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
// 省略其他方法 ...
}
要使用 PerpetualCache
,只需在 MyBatis 的配置文件中指定缓存的类型为 PerpetualCache
即可。例如:
<cache type="org.apache.ibatis.cache.impl.PerpetualCache"/>
PerpetualCache
的简单实现使得它在内存中存储缓存数据的操作非常高效,适用于对实时性要求较高的场景。但需要注意的是,由于 PerpetualCache
不会自动清理缓存数据,如果缓存数据量过大,可能会导致内存溢出的问题,因此在使用时需要谨慎考虑缓存数据的大小和生命周期。
FifoCache
FifoCache
是 MyBatis 中的一个缓存实现类,它是 PerpetualCache
的一个装饰器(Decorator)。FifoCache
的全称是 First In, First Out Cache,即先进先出缓存,这是一种缓存的换出策略。它在 PerpetualCache
的基础上添加了一个队列,用于记录缓存数据的访问顺序。当缓存数据的数量达到一定阈值(默认 1024)时,FifoCache
会按照数据的访问顺序,淘汰最早被访问的数据,以保持缓存数据的新鲜度。
FifoCache
的实现原理是通过一个队列来记录缓存数据的插入顺序。当有新的数据需要放入缓存时,它会被添加到队列的末尾;当缓存数据的数量达到一定阈值时,FifoCache
会从队列的头部开始移除一条数据,即最早被访问的数据。
public class FifoCache implements Cache {
private final Cache delegate;
// 通过这个队列记录缓存的顺序
private final Deque<Object> keyList;
private int size;
public FifoCache(Cache delegate) {
this.delegate = delegate;
this.keyList = new LinkedList<Object>();
this.size = 1024;
}
public void setSize(int size) {
// 可以手动设置清理阈值
this.size = size;
}
@Override
public void putObject(Object key, Object value) {
cycleKeyList(key);
// 新增时判断是否要清理
delegate.putObject(key, value);
}
private void cycleKeyList(Object key) {
keyList.addLast(key);
if (keyList.size() > size) {
Object oldestKey = keyList.removeFirst();
delegate.removeObject(oldestKey);
}
}
@Override
public Object getObject(Object key) {
return delegate.getObject(key);
}
// 省略其他方法 ...
}
要使用 FifoCache
,只需在 MyBatis 的配置文件中将需要被包装的缓存实现类包装在 FifoCache
中即可。例如:
<cache type="org.apache.ibatis.cache.decorators.FifoCache"/>
FifoCache
适用于需要按照访问顺序进行淘汰的场景,通常用于控制缓存数据的大小,避免缓存数据过多导致内存溢出或者性能下降。需要注意的是,由于 FifoCache
需要维护一个队列,可能会略微增加缓存的读写操作的开销,因此在性能要求较高的场景下需要进行评估和测试。
LruCache
LruCache
是 MyBatis 中的一个缓存实现类,它是 PerpetualCache
的一个装饰器(Decorator)。LruCache 的全称是 Least Recently Used Cache,即最近最少使用缓存,这是一种缓存的换出策略。它基于最近最少使用算法(LRU),在缓存数据达到一定大小时,会淘汰最近最少被访问的数据,以保持缓存数据的新鲜度。
LruCache
的实现原理是通过一个 LinkedHashMap 来存储缓存数据,并按照访问顺序进行排序。每当缓存数据被访问时,对应的条目会被移动到 LinkedHashMap 的尾部,表示最近被使用过。当缓存数据达到一定大小时,LruCache
会从 LinkedHashMap 的头部开始移除一条数据,即最近最少被访问的数据。
LinkedHashMap 是 Java 中的一个特殊 HashMap 实现,它除了具备 HashMap 的基本功能外,还额外维护了一个双向链表,用于记录元素的插入顺序或者访问顺序。这个链表可以按照插入顺序或者访问顺序(LRU)来排列元素。
在 LRUCache 中,我们希望根据元素的访问顺序来淘汰最近最少被使用的数据。这就需要利用 LinkedHashMap 的特性:
- 每当元素被访问时,LinkedHashMap 会将该元素移动到链表的末尾:这意味着当我们通过 get(key) 方法访问 LRUCache 中的某个元素时,LinkedHashMap 会将该元素移动到链表的末尾,表示它是最近被使用过的元素。
- 链表的末尾表示最近被使用过的元素:由于 LinkedHashMap 维护的链表是双向链表,末尾节点即为最近被使用过的节点,头部节点即为最早被使用过的节点。
通过 LinkedHashMap 的这种特性,LRUCache 可以在元素被访问时将其移动到链表的末尾,从而实现了最近被使用的元素位于链表末尾的效果。当需要淘汰缓存数据时,LRUCache 可以从链表的头部开始遍历,找到最早被使用的节点,并将其从缓存中移除。
public class LruCache implements Cache {
private final Cache delegate;
// 通过这个 Map 来存储访问顺序,构造的时候进行初始化,实际上是 LinkedHashMap。
private Map<Object, Object> keyMap;
private Object eldestKey;
public LruCache(Cache delegate) {
this.delegate = delegate;
setSize(1024);
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
public void setSize(final int size) {
keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
private static final long serialVersionUID = 4267176411845948333L;
@Override
protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
// 重写是否要删除最早元素的方法,加入大小判断的逻辑
boolean tooBig = size() > size;
if (tooBig) {
eldestKey = eldest.getKey();
}
return tooBig;
}
};
}
@Override
public void putObject(Object key, Object value) {
delegate.putObject(key, value);
cycleKeyList(key);
}
private void cycleKeyList(Object key) {
keyMap.put(key, key);
if (eldestKey != null) {
delegate.removeObject(eldestKey);
eldestKey = null;
}
}
@Override
public Object getObject(Object key) {
keyMap.get(key); // 每次访问 Map 中的元素,LinkedHashMap 的元素位置都会变化,最先访问的放在最后面。
return delegate.getObject(key);
}
// 省略其他方法 ...
}
要使用 LruCache
,只需在 MyBatis 的配置文件中将需要被包装的缓存实现类包装在 LruCache
中即可。例如:
<cache type="org.apache.ibatis.cache.decorators.LruCache"/>
LruCache
适用于需要按照最近访问顺序进行淘汰的场景,通常用于控制缓存数据的大小,避免缓存数据过多导致内存溢出或者性能下降。需要注意的是,由于 LruCache
需要维护一个 LinkedHashMap,可能会略微增加缓存的读写操作的开销,因此在性能要求较高的场景下需要进行评估和测试。
ScheduledCache
ScheduledCache
是 MyBatis 中的一个缓存实现类,它是 PerpetualCache
的一个装饰器(Decorator)。ScheduledCache
提供了定时清理缓存数据的功能,可以定期清理过期的缓存数据,以避免缓存数据过多导致内存溢出或者性能下降。
ScheduledCache
的实现原理是内部维护了两个属性,清理时间的间隔(clearInterval),上次清理的时间(lastClear)。在 remove,get,put 的时候,会先判断一下时间,当前时间减去上次清理的时间是否大于间隔时间(默认一小时),如果是就直接清。并且将当前时间赋值给 lastClear。
这种清理是有问题的,这种将清理的触发的操作绑定给了操作,那如果说不操作,其实这个清理时间就没有意义,也没有清理操作,还是占用着内存空间。
public class ScheduledCache implements Cache {
private final Cache delegate;
// 清理时间的间隔
protected long clearInterval;
// 上次清理的时间
protected long lastClear;
public ScheduledCache(Cache delegate) {
this.delegate = delegate;
this.clearInterval = 60 * 60 * 1000; // 1 hour
this.lastClear = System.currentTimeMillis();
}
public void setClearInterval(long clearInterval) {
this.clearInterval = clearInterval;
}
@Override
public int getSize() {
clearWhenStale();
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
clearWhenStale();
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
return clearWhenStale() ? null : delegate.getObject(key);
}
@Override
public Object removeObject(Object key) {
clearWhenStale();
return delegate.removeObject(key);
}
@Override
public void clear() {
lastClear = System.currentTimeMillis();
delegate.clear();
}
private boolean clearWhenStale() {
if (System.currentTimeMillis() - lastClear > clearInterval) {
clear();
return true;
}
return false;
}
// 省略其他方法 ...
}
要使用 ScheduledCache
,只需在 MyBatis 的配置文件中将需要被包装的缓存实现类包装在 ScheduledCache
中即可。例如:
<cache type="org.apache.ibatis.cache.decorators.ScheduledCache">
<property name="clearInterval" value="60000"/> <!-- 清理间隔时间,单位为毫秒 -->
</cache>
SoftCache
将 Key 和 Value 包装为 SoftEntry,保存在 delegate 中,并且自己持有 hardLinksToAvoidGarbageCollection(强引用的集合)和一个queueOfGarbageCollectedEntries(引用队列),在 put 和 remove 的时候,在调用 delegate 之前,会先从引用队列里面获取值,如果能获取值,就从 delegate 中移除。在 get 的时候会先从 delegate 获取,如果这个值存在,并且没有被 GC,给他增加强引用(添加到 hardLinksToAvoidGarbageCollection 里面去),并且如果强引用集合大于 numberOfHardLinks(默认是 256),就移除队尾元素。
public class SoftCache implements Cache {
// 队列,持有一个强引用
private final Deque<Object> hardLinksToAvoidGarbageCollection;
// 软引用被回收之后放置的集合
private final ReferenceQueue<Object> queueOfGarbageCollectedEntries;
private final Cache delegate;
// 有多少个强引用,默认是256
private int numberOfHardLinks;
public SoftCache(Cache delegate) {
this.delegate = delegate;
this.numberOfHardLinks = 256;
this.hardLinksToAvoidGarbageCollection = new LinkedList<>();
this.queueOfGarbageCollectedEntries = new ReferenceQueue<>();
}
@Override
public String getId() {
return delegate.getId();
}
// removeGarbageCollectedItems 方法是干嘛的?
@Override
public int getSize() {
removeGarbageCollectedItems();
return delegate.getSize();
}
public void setSize(int size) {
this.numberOfHardLinks = size;
}
@Override
public void putObject(Object key, Object value) {
removeGarbageCollectedItems();
// 将 key 和 value 包装为 SoftEntry 值。
delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries));
}
@Override
public Object getObject(Object key) {
Object result = null;
@SuppressWarnings("unchecked") // assumed delegate cache is totally managed by this cache
// 先从 delegate 中获取元素,这个元素是 SoftReference,
SoftReference<Object> softReference = (SoftReference<Object>) delegate.getObject(key);
if (softReference != null) {
result = softReference.get();
if (result == null) {
delegate.removeObject(key);
} else {
// 如果不是null,说明当前的这个对象对象还没有被回收了,所以,添加到 hardLinksToAvoidGarbageCollection 里面,增加强引用关系
// See #586 (and #335) modifications need more than a read lock
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.addFirst(result);
// 如果大于 numberOfHardLinks,就将 hardLinksToAvoidGarbageCollection 里面的尾元素移除
if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) {
hardLinksToAvoidGarbageCollection.removeLast();
}
}
}
}
return result;
}
@Override
public Object removeObject(Object key) {
removeGarbageCollectedItems();
return delegate.removeObject(key);
}
@Override
public void clear() {
synchronized (hardLinksToAvoidGarbageCollection) {
hardLinksToAvoidGarbageCollection.clear();
}
removeGarbageCollectedItems();
delegate.clear();
}
// 看这里的逻辑,好多都调用了这个方法,从 queueOfGarbageCollectedEntries 队列里面出队一个元素
// 如果有,说明这个元素已经被 GC 要被 GC 回收了,那么就需要将 delegate 中的这个元素也移除掉
// 问题?hardLinksToAvoidGarbageCollection 里面要不要移除?
// 不需要,因为从 queueOfGarbageCollectedEntries 里面能出来的话,说明他已经是个垃圾了,没有强引用啦。
private void removeGarbageCollectedItems() {
SoftEntry sv;
while ((sv = (SoftEntry) queueOfGarbageCollectedEntries.poll()) != null) {
delegate.removeObject(sv.key);
}
}
private static class SoftEntry extends SoftReference<Object> {
private final Object key;
SoftEntry(Object key, Object value, ReferenceQueue<Object> garbageCollectionQueue) {
super(value, garbageCollectionQueue);
this.key = key;
}
}
}
WeakCache
这个和 SoftCache
差不多,看看就可以,我这里就不写了…
LoggingCache
LoggingCache
是 MyBatis 中的一个缓存装饰器,它用于在缓存操作的前后打印日志,以便于调试和监控缓存的使用情况。
LoggingCache
主要是为了方便开发者跟踪缓存的使用情况和了解缓存操作的性能。它会在缓存的读取和写入操作之前后打印相应的日志信息,包括缓存键、缓存值、操作类型等。这样可以帮助开发者快速定位缓存相关的问题,并且可以通过日志信息了解缓存的命中率、缓存数据的更新频率等。
public class LoggingCache implements Cache {
private final Log log;
private final Cache delegate;
// 请求次数
protected int requests = 0;
// 命中次数
protected int hits = 0;
public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(getId());
}
@Override
public Object getObject(Object key) {
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
hits++;
}
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}
private double getHitRatio() {
return (double) hits / (double) requests;
}
// 省略其他方法 ...
}
要使用 LoggingCache
,只需在 MyBatis 的配置文件中将需要被包装的缓存实现类包装在 LoggingCache
中即可。例如:
<cache type="org.apache.ibatis.cache.decorators.LoggingCache"/>
通过添加 LoggingCache
,MyBatis 在执行缓存操作时会打印相应的日志信息,方便开发者进行调试和监控。需要注意的是,由于日志记录会对性能产生一定的影响,因此在生产环境中建议关闭 LoggingCache
,或者仅在调试阶段开启。
BlockingCache
BlockingCache
是 MyBatis 中的一个缓存装饰器,它提供了线程安全的功能。在多线程环境下,当多个线程同时访问同一个 key 对应的缓存时,BlockingCache
可以确保只有一个线程可以执行查询操作,其他线程会等待直到查询完成后再获取结果。
BlockingCache
的实现原理是利用了 Java 中的 ReentrantLock
(可重入锁)。当一个线程试图获取某个 key 对应的缓存时,如果发现该 key 对应的缓存正在被其他线程加载,则会阻塞当前线程,直到缓存加载完成。这样可以避免多个线程同时进行重复的查询操作,提高了缓存的利用率和系统的性能。
public class BlockingCache implements Cache {
// 尝试获取锁的超时时间
private long timeout;
private final Cache delegate;
// 为每一个 Key 搭配一个重入锁,构造时初始化为 ConcurrentHashMap
private final ConcurrentHashMap<Object, ReentrantLock> locks;
public BlockingCache(Cache delegate) {
this.delegate = delegate;
this.locks = new ConcurrentHashMap<Object, ReentrantLock>();
}
@Override
public void putObject(Object key, Object value) {
try {
delegate.putObject(key, value);
} finally {
// 释放锁
releaseLock(key);
}
}
@Override
public Object getObject(Object key) {
// 获取锁
acquireLock(key);
Object value = delegate.getObject(key);
if (value != null) {
// 释放锁
releaseLock(key);
}
return value;
}
@Override
public Object removeObject(Object key) {
// despite of its name, this method is called only to release locks
releaseLock(key);
return null;
}
private ReentrantLock getLockForKey(Object key) {
ReentrantLock lock = new ReentrantLock();
ReentrantLock previous = locks.putIfAbsent(key, lock);
return previous == null ? lock : previous;
}
private void acquireLock(Object key) {
Lock lock = getLockForKey(key);
if (timeout > 0) {
try {
// 在指定的时间内尝试获取锁。如果超过这个时间还未获取到锁,则放弃获取,并返回 false。
boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
if (!acquired) {
throw new CacheException("Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
}
} catch (InterruptedException e) {
throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
}
} else {
lock.lock();
}
}
private void releaseLock(Object key) {
ReentrantLock lock = locks.get(key);
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
// 省略其他方法 ...
}
要使用 BlockingCache
,只需在 MyBatis 的配置文件中将需要被包装的缓存实现类包装在 BlockingCache
中即可。例如:
<cache type="org.apache.ibatis.cache.decorators.BlockingCache"/>
通过添加 BlockingCache
,MyBatis 在使用该缓存时就会自动添加了线程安全的功能。需要注意的是,由于 BlockingCache
使用了额外的线程同步机制,可能会略微增加缓存操作的开销,因此在性能要求较高的场景下需要进行评估和测试。
SerializbledCache
要用这个缓存,必须要实现 Serializable
接口,在 put 和 get 的时候会出现序列化和反序列化。序列化的目的是为了深拷贝,深拷贝是为了什么?安全。
SerializedCache
是 MyBatis 中的一个缓存装饰器(Decorator),它用于将缓存中的数据进行序列化和反序列化,以支持跨 JVM 实例的共享。
SerializedCache
的主要作用是将缓存中的对象转换成字节流进行存储,从而可以在不同的 JVM 实例之间进行传输和共享。这样可以在分布式系统中使用相同的缓存实例,从而提高了缓存数据的共享和利用率。
具体来说,当数据写入到缓存中时,SerializedCache
会将对象转换成字节流,并存储到底层的缓存中。当数据从缓存中读取时,SerializedCache
会将存储的字节流反序列化成对象,并返回给调用方使用。
public class SerializedCache implements Cache {
private final Cache delegate;
public SerializedCache(Cache delegate) {
this.delegate = delegate;
}
@Override
public void putObject(Object key, Object object) {
// 主要看这个,Value 必须要实现 Serializable 接口,会有序列化。
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);
}
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
// 反序列化
return object == null ? null : deserialize((byte[]) object);
}
// 这里直接利用序列化来拷贝了一份,这不就是 copyOnWrite 吗
// 序列化的操作没有啥可说的,就这里为啥要序列化?
// 深拷贝呀,
private byte[] serialize(Serializable value) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos)) {
oos.writeObject(value);
oos.flush();
return bos.toByteArray();
} catch (Exception e) {
throw new CacheException("Error serializing object. Cause: " + e, e);
}
}
// 反序列化
private Serializable deserialize(byte[] value) {
SerialFilterChecker.check();
Serializable result;
try (ByteArrayInputStream bis = new ByteArrayInputStream(value);
ObjectInputStream ois = new CustomObjectInputStream(bis)) {
result = (Serializable) ois.readObject();
} catch (Exception e) {
throw new CacheException("Error deserializing object. Cause: " + e, e);
}
return result;
}
public static class CustomObjectInputStream extends ObjectInputStream {
public CustomObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
return Resources.classForName(desc.getName());
}
}
}
要使用 SerializedCache
,只需在 MyBatis 的配置文件中将需要被包装的缓存实现类包装在 SerializedCache
中即可。例如:
<cache type="org.apache.ibatis.cache.decorators.SerializedCache"/>
需要注意的是,使用 SerializedCache
可能会增加序列化和反序列化的开销,并且存储的数据量会比原始对象要大,因此在使用时需要权衡性能和资源占用。通常情况下,建议在需要跨 JVM 实例共享缓存数据时使用 SerializedCache
。
TransactionalCache
TransactionalCache
是 MyBatis 中的一个缓存装饰器(Decorator),它提供了事务性缓存的功能。事务性缓存确保缓存中的数据与数据库中的数据保持一致,只有在事务成功提交后,缓存中的数据才会被更新或者删除。
TransactionalCache
的主要作用是在事务提交时,将缓存中的数据同步到数据库中。它通过拦截事务提交的操作,在事务成功提交后,更新或者删除缓存中的数据,以保持缓存和数据库的一致性。
具体来说,当事务提交时,TransactionalCache
会根据事务的操作类型(插入、更新、删除)来更新或者删除缓存中的相应数据。这样可以确保缓存中的数据与数据库中的数据保持一致,避免了因为缓存数据与数据库数据不一致而导致的数据异常问题。
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
// 标志位
private boolean clearOnCommit;
// commit 的时候需要添加到缓存里面的实体
private final Map<Object, Object> entriesToAddOnCommit;
// get 的时候没有命中缓存的 key 的集合
private final Set<Object> entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap<>();
this.entriesMissedInCache = new HashSet<>();
}
@Override
public Object getObject(Object key) {
Object object = delegate.getObject(key);
if (object == null) {
// 在 get 的时候如果没有值,会放在 entriesMissedInCache 里面
entriesMissedInCache.add(key);
}
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public void putObject(Object key, Object object) {
// put 不会直接调用 delegate 方法,会先放在 entriesToAddOnCommit 里面
entriesToAddOnCommit.put(key, object);
}
@Override
public Object removeObject(Object key) {
// 不能手动移除
return null;
}
@Override
public void clear() {
clearOnCommit = true;
entriesToAddOnCommit.clear();
}
// 只有 commit 的时候,会先将 delegate 清除,之后将 entriesToAddOnCommit 里面的添加到 delegate 里面
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
public void rollback() {
unlockMissedEntries();
reset();
}
private void reset() {
clearOnCommit = false;
entriesToAddOnCommit.clear();
entriesMissedInCache.clear();
}
private void flushPendingEntries() {
// 这里可以用 PutAll 操作
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
// 对于缓存没有命中,直接放一个 null
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
// 移除
private void unlockMissedEntries() {
for (Object entry : entriesMissedInCache) {
try {
delegate.removeObject(entry);
} catch (Exception e) {
log.warn("Unexpected exception while notifying a rollback to the cache adapter. "
+ "Consider upgrading your cache adapter to the latest version. Cause: " + e);
}
}
}
}
要使用 TransactionalCache
,只需在 MyBatis 的配置文件中将需要被包装的缓存实现类包装在 TransactionalCache
中即可。例如:
<cache type="org.apache.ibatis.cache.decorators.TransactionalCache"/>
需要注意的是,TransactionalCache
通常需要在事务管理器(TransactionManager)的支持下才能正常工作,因为它需要拦截事务提交的操作。另外,由于缓存与数据库的同步操作可能会对性能产生一定的影响,因此在性能要求较高的场景下需要进行评估和测试。
- 点赞
- 收藏
- 关注作者
评论(0)