缓存
缓存即将数据存储在内存上,传统的数据库,大量的数据都会存储在硬盘之上,而硬盘的读写效率是大大低于内存的,所以缓存的价值是在程序和数据库之间搭建一个桥梁,将一部分数据存储在内存,提高用户的查询效率。这是一种典型的空间换时间的优化策略。接下来我们将会进入 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)的支持下才能正常工作,因为它需要拦截事务提交的操作。另外,由于缓存与数据库的同步操作可能会对性能产生一定的影响,因此在性能要求较高的场景下需要进行评估和测试。