MyBatis 缓存机制深度解析:从 Cache 接口到装饰器模式 MyBatis 缓存是提升查询性能的核心组件,通过减少重复数据库访问,大幅降低系统开销。其缓存体系基于 Cache 接口 构建,采用 装饰器模式 实现 “基础存储 + 功能增强” 的灵活扩展,同时通过 CacheKey 保证缓存键的唯一性。从 “缓存体系架构→Cache 接口与实现→CacheKey 生成逻辑→实战配置” 四个维度,彻底拆解 MyBatis 缓存的底层机制。
MyBatis 缓存体系总览 MyBatis 提供 两级缓存 ,本质上均基于 Cache 接口实现,核心区别在于 “作用范围” 和 “生命周期”:
缓存级别
作用范围
生命周期
默认状态
底层核心实现
典型场景
一级缓存
SqlSession 内部(会话级)
随 SqlSession 关闭而销毁
开启
PerpetualCache(HashMap)
同一会话内的重复查询(如单事务内多次查同一数据)
二级缓存
Mapper namespace(接口级)
随 MyBatis 应用生命周期
关闭
PerpetualCache + 装饰器(如 LRU 淘汰、序列化)
跨会话的重复查询(如多用户查询同一商品信息)
核心设计思想:装饰器模式 MyBatis 缓存的灵活性源于 装饰器模式 :
基础组件 :PerpetualCache 实现最基本的缓存存储(基于 HashMap),是所有缓存的 “底层容器”;
装饰器组件 :如 LruCache(LRU 淘汰)、BlockingCache(并发控制)、SerializedCache(序列化)等,通过包装 PerpetualCache 或其他装饰器,动态增强缓存功能;
组合能力 :可按需组合多个装饰器(如 “LRU 淘汰 + 序列化 + 日志”),满足复杂业务需求。
Cache 接口:缓存的标准定义 Cache 接口是 MyBatis 缓存的顶层规范,定义了缓存的 7 个核心行为,所有缓存实现类均需遵守该接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public interface Cache { String getId () ; void putObject (Object key, Object value) ; Object getObject (Object key) ; Object removeObject (Object key) ; void clear () ; int getSize () ; ReadWriteLock getReadWriteLock () ; }
接口设计要点:
缓存键(key) :统一使用 CacheKey 类型(而非简单 String/Integer),确保不同查询的唯一性;
无状态设计 :接口方法仅依赖入参,不保存额外状态,便于装饰器组合;
功能最小化 :仅定义基础缓存操作,复杂功能(如淘汰、同步)通过装饰器扩展。
Cache 接口实现类解析 MyBatis 提供 10+ Cache 实现类,可分为 基础存储类 和 功能装饰器类 ,以下按功能分类解析核心实现。
1. 基础存储类:PerpetualCache(HashMap 实现) PerpetualCache 是 MyBatis 缓存的 基础实现 ,仅提供 “键值对存储” 功能,无淘汰、同步等增强,是所有缓存的底层容器。
源码核心逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 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 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(); } @Override public int getSize () { return cache.size(); } }
特点与适用场景:
优点 :简单高效,基于 HashMap 实现 O (1) 读写复杂度;
缺点 :无缓存淘汰机制(满了不会自动删除)、无并发控制(多线程读写可能不安全);
适用场景 :作为一级缓存的底层存储(SqlSession 内单线程访问,无需淘汰),或作为二级缓存的基础容器(需配合装饰器使用)。
2. 缓存淘汰策略装饰器:解决 “缓存满了怎么办” 当缓存达到容量上限时,需通过淘汰策略删除部分数据,MyBatis 提供 3 种常用淘汰装饰器:
(1)FifoCache:先进先出(FIFO) 按 “缓存存入顺序” 淘汰数据,最早存入的缓存项优先被删除,适用于 “数据时效性按顺序变化” 的场景(如日志查询)。
源码核心逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 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<>(); this .size = 1024 ; } @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); } } }
关键逻辑:
用 LinkedList 记录缓存项顺序,新项入队尾,满了删队头;
仅在 putObject 时触发淘汰,getObject 不改变顺序。
(2)LruCache:最近最少使用(LRU) 按 “缓存访问频率” 淘汰数据,最近最少被访问 的缓存项优先被删除,适用于 “热点数据集中” 的场景(如商品详情查询),是最常用的淘汰策略。
源码核心逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class LruCache implements Cache { private final Cache delegate; private Map<Object, Object> keyMap; private Object eldestKey; public LruCache (Cache delegate) { this .delegate = delegate; this .setSize(1024 ); } public void setSize (final int size) { keyMap = new LinkedHashMap<Object, Object>(size, 0.75F , true ) { @Override protected boolean removeEldestEntry (Entry<Object, Object> eldest) { boolean tooBig = this .size() > size; if (tooBig) { LruCache.this .eldestKey = eldest.getKey(); } return tooBig; } }; } @Override public void putObject (Object key, Object value) { delegate.putObject(key, value); cycleKeyList(key); } @Override public Object getObject (Object key) { keyMap.get(key); return delegate.getObject(key); } private void cycleKeyList (Object key) { keyMap.put(key, key); if (eldestKey != null ) { delegate.removeObject(eldestKey); eldestKey = null ; } } }
关键逻辑:
使用 LinkedHashMap 并设置 accessOrder=true,使 Map 按 “访问顺序” 排序(get/put 会将 key 移到队尾);
removeEldestEntry 方法在 Map 满时返回 true,标记 eldestKey 为待淘汰项,随后在 cycleKeyList 中删除。
(3)SoftCache/WeakCache:基于引用的淘汰(GC 触发) 通过 Java 的 软引用(SoftReference) 和 弱引用(WeakReference) 实现缓存淘汰,当 JVM 内存不足时,自动回收缓存项,适用于 “非核心数据缓存”(如临时统计数据)。
核心区别:
实现类
引用类型
GC 回收时机
适用场景
SoftCache
软引用
JVM 内存不足时回收
允许缓存项临时存活,内存不足再淘汰
WeakCache
弱引用
下次 GC 时立即回收
缓存项生命周期短,随 GC 快速清理
SoftCache 关键源码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 public class SoftCache implements Cache { private final Cache delegate; private final Deque<Object> hardLinksToAvoidGarbageCollection; private final ReferenceQueue<Object> queueOfGarbageCollectedEntries; private int numberOfHardLinks = 256 ; @Override public void putObject (Object key, Object value) { removeGarbageCollectedItems(); delegate.putObject(key, new SoftEntry(key, value, queueOfGarbageCollectedEntries)); } @Override public Object getObject (Object key) { Object result = null ; SoftReference<Object> softReference = (SoftReference) delegate.getObject(key); if (softReference != null ) { result = softReference.get(); if (result == null ) { delegate.removeObject(key); } else { synchronized (hardLinksToAvoidGarbageCollection) { hardLinksToAvoidGarbageCollection.addFirst(result); if (hardLinksToAvoidGarbageCollection.size() > numberOfHardLinks) { hardLinksToAvoidGarbageCollection.removeLast(); } } } } return result; } 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> queue) { super (value, queue); this .key = key; } } }
3. 并发控制装饰器:解决 “多线程安全问题” 当多个线程同时访问缓存时,可能出现 “并发修改异常” 或 “重复查询数据库”,MyBatis 提供 2 种并发控制装饰器:
(1)SynchronizedCache:同步锁(简单并发控制) 为缓存的所有方法添加 synchronized 关键字,保证单线程访问,适用于 “低并发” 场景。
源码核心逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public class SynchronizedCache implements Cache { private final Cache delegate; public SynchronizedCache (Cache delegate) { this .delegate = delegate; } @Override public synchronized int getSize () { return delegate.getSize(); } @Override public synchronized void putObject (Object key, Object value) { delegate.putObject(key, value); } @Override public synchronized Object getObject (Object key) { return delegate.getObject(key); } }
特点:
优点:实现简单,保证线程安全;
缺点:全局锁,高并发下性能差(所有缓存操作串行执行)。
(2)BlockingCache:阻塞锁(避免重复查询) 为 每个缓存 key 单独加锁 ,保证 “同一 key 仅一个线程查询数据库”,其他线程阻塞等待,适用于 “高并发下同一 key 频繁查询” 的场景(如热点商品详情)。
源码核心逻辑 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 public class BlockingCache implements Cache { private long timeout; private final Cache delegate; private final ConcurrentHashMap<Object, ReentrantLock> locks = new ConcurrentHashMap<>(); @Override public Object getObject (Object key) { acquireLock(key); Object value = delegate.getObject(key); if (value != null ) { releaseLock(key); } return value; } @Override public void putObject (Object key, Object value) { try { delegate.putObject(key, value); } finally { releaseLock(key); } } private void acquireLock (Object key) { ReentrantLock lock = getLockForKey(key); if (timeout > 0 ) { try { boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS); if (!acquired) { throw new CacheException("获取锁超时:key=" + key); } } catch (InterruptedException e) { throw new CacheException("获取锁被中断:key=" + key); } } else { lock.lock(); } } private void releaseLock (Object key) { ReentrantLock lock = locks.get(key); if (lock.isHeldByCurrentThread()) { lock.unlock(); } } private ReentrantLock getLockForKey (Object key) { return locks.computeIfAbsent(key, k -> new ReentrantLock()); } }
关键逻辑:
细粒度锁 :每个 key 对应独立的 ReentrantLock,不同 key 可并行查询;
锁释放时机 :缓存命中时在 getObject 释放,未命中时在 putObject 释放(确保只有一个线程查库);
超时保护 :支持 timeout 配置,避免死锁导致线程永久阻塞。
4. 其他功能装饰器
装饰器类
核心功能
适用场景
LoggingCache
记录缓存命中次数和命中率(日志)
缓存性能监控(如 debug 时查看命中率)
ScheduledCache
周期性清理缓存(如每小时清空一次)
缓存数据有固定过期时间(如小时级统计数据)
SerializedCache
对缓存值进行序列化 / 反序列化
分布式缓存(如多节点共享缓存需序列化)
CacheKey:缓存键的生成逻辑(缓存命中的关键) MyBatis 缓存的 key 并非简单的 “参数值”,而是通过 CacheKey 类生成的复合键 ,确保 “不同查询不会共用同一个 key”,避免缓存命中错误。
1. CacheKey 的组成部分 一个 CacheKey 由 5 个核心要素共同决定,任意一个要素不同,生成的 key 就不同 :
MappedStatement ID :SQL 对应的唯一标识(如 com.example.UserMapper.selectUserById);
RowBounds :查询结果范围(offset 偏移量和 limit 最大行数);
SQL 语句 :解析后的原始 SQL(含 ? 占位符,如 SELECT * FROM user WHERE id = ?);
查询参数 :用户传入的实际参数(如 id=1);
Environment ID :数据库环境标识(多数据源场景下,不同数据源的同一查询需不同 key)。
2. CacheKey 核心源码解析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 public class CacheKey implements Cloneable , Serializable { private static final int DEFAULT_MULTIPLIER = 37 ; private static final int DEFAULT_HASHCODE = 17 ; private final int multiplier; private int hashcode; private long checksum; private int count; private List<Object> updateList; public CacheKey () { this .hashcode = DEFAULT_HASHCODE; this .multiplier = DEFAULT_MULTIPLIER; this .count = 0 ; this .updateList = new ArrayList<>(); } public void update (Object object) { int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); count++; checksum += baseHashCode; baseHashCode *= count; hashcode = multiplier * hashcode + baseHashCode; updateList.add(object); } public void updateAll (Object[] objects) { for (Object o : objects) { update(o); } } @Override public boolean equals (Object object) { if (this == object) return true ; if (!(object instanceof CacheKey)) return false ; CacheKey cacheKey = (CacheKey) object; if (hashcode != cacheKey.hashcode) return false ; if (checksum != cacheKey.checksum) return false ; if (count != cacheKey.count) return false ; for (int i = 0 ; i < updateList.size(); i++) { Object thisObj = updateList.get(i); Object thatObj = cacheKey.updateList.get(i); if (!ArrayUtil.equals(thisObj, thatObj)) return false ; } return true ; } @Override public int hashCode () { return hashcode; } }
关键逻辑:
哈希计算 :通过 update 方法累加每个要素的哈希值,使用 “37*hashcode + 要素哈希” 的公式,减少哈希碰撞;
equality 判断 :先通过 hashcode、checksum、count 快速排除不相等的 key,再逐个比较要素列表,确保完全一致;
防碰撞 :checksum 字段进一步降低哈希碰撞概率,避免不同要素组合生成相同 hashcode。
3. CacheKey 生成时机 在 Executor 执行查询时,会调用 createCacheKey 方法生成 CacheKey:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Override public CacheKey createCacheKey (MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) { if (closed) throw new ExecutorException("Executor 已关闭" ); CacheKey cacheKey = new CacheKey(); cacheKey.update(ms.getId()); cacheKey.update(rowBounds.getOffset()); cacheKey.update(rowBounds.getLimit()); cacheKey.update(boundSql.getSql()); cacheKey.updateAll(getParameterObjects(parameter, boundSql)); if (configuration.getEnvironment() != null ) { cacheKey.update(configuration.getEnvironment().getId()); } return cacheKey; }
实战:二级缓存配置与装饰器组合 一级缓存默认开启(无需配置),二级缓存需手动开启,且可通过配置组合多个装饰器,实现 “LRU 淘汰 + 周期性清理 + 序列化” 等功能。
1. 全局开启二级缓存(mybatis-config.xml) 1 2 3 4 5 6 <configuration > <settings > <setting name ="cacheEnabled" value ="true" /> </settings > </configuration >
2. Mapper 接口开启二级缓存(Mapper.xml) 通过 <cache> 标签配置二级缓存,支持指定装饰器组合:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <mapper namespace ="com.example.mapper.UserMapper" > <cache eviction ="LRU" flushInterval ="60000" size ="1024" readOnly ="false" type ="org.apache.ibatis.cache.impl.PerpetualCache" > <property name ="timeout" value ="5000" /> </cache > <select id ="selectUserById" resultType ="User" useCache ="true" > SELECT * FROM user WHERE id = #{id} </select > </mapper >
关键配置说明:
eviction="LRU" :使用 LruCache 装饰器,实现 LRU 淘汰;
flushInterval="60000" :使用 ScheduledCache 装饰器,每 60 秒清理一次缓存;
readOnly="false" :使用 SerializedCache 装饰器,返回缓存对象的拷贝(避免并发修改);
useCache="true" :指定该查询使用二级缓存(默认 true,insert/update/delete 会自动清空缓存)。
总结:MyBatis 缓存的核心价值与适用场景 MyBatis 缓存通过 Cache 接口和装饰器模式,实现了 “灵活扩展 + 性能优化” 的双重目标,核心价值体现在:
性能提升 :减少重复数据库访问,尤其对热点数据查询(如商品详情),性能提升显著;
灵活扩展 :装饰器模式支持按需组合功能(淘汰策略、并发控制、序列化等),满足不同业务需求;
低侵入性 :无需修改业务代码,通过配置即可开启和定制缓存;
安全性 :CacheKey 确保缓存键的唯一性,避免命中错误数据;BlockingCache 等装饰器保证并发安全。
适用场景与注意事项:
适用场景 :查询频繁、修改少、数据一致性要求不高的场景(如商品分类、静态配置);
不适用场景 :实时性要求高的数据(如用户余额)、频繁修改的数据(如订单状态);
注意事项:
二级缓存是 namespace 级别,跨 namespace 的关联查询可能导致数据不一致;
缓存对象需实现 Serializable 接口(如使用 SerializedCache 或分布式缓存);
insert/update/delete 操作会自动清空对应 namespace 的二级缓存,确保数据一致性
v1.3.10