MyBatis二级缓存基于装饰器模式动态组装,以PerpetualCache为底,按需叠加LruCache(LRU淘汰)、FifoCache(FIFO淘汰)、LoggingCache(操作日志)等装饰器,各司其职且顺序关键。

MyBatis 缓存设计中,装饰器模式不是“加功能”的可选技巧,而是整个二级缓存能力扩展的底层骨架。它不靠继承、不改核心,而是把一个基础缓存(PerpetualCache)像套娃一样层层包裹,每层只专注一件事:比如淘汰策略、线程安全、日志记录或自动序列化。
LruCache:让缓存“记住谁该被踢出去”
LruCache 并不自己存数据,而是持有一个被装饰的 Cache 实例(比如 PerpetualCache),再额外维护一个 LinkedHashMap 来记录访问顺序。每次 get 或 put 时,它先更新访问序号,再检查是否超限;一旦 size 超过配置值,就移除最久未用的 key,并同步调用 delegate.removeObject()。
- 它不改变底层存储逻辑,只干预“何时清理”
- 默认启用,无需显式配置
eviction="LRU"就会自动生效 - 适合读多写少、热点数据集中的场景,比如用户资料、商品类目
FifoCache:按“进门先后”排队淘汰
FifoCache 同样委托给底层 Cache 存取数据,但它用一个 Deque(双端队列)记录 key 的插入顺序。每次 put 时把 key 加入队尾;当 size 达到上限,就从队首取出最早进来的 key 并删除——完全不看使用频次,只认时间戳。
- 实现极简,开销比 LRU 小,适合对淘汰公平性要求高于热点识别的场景
- 需在
<cache>中显式指定:eviction="FIFO" - 注意:若缓存命中率低,可能造成“刚进就出”,效果反不如 LRU
LoggingCache:给所有操作加一层“记事本”
LoggingCache 是典型的“旁路增强型”装饰器。它不做任何业务判断,只在每次 putObject、getObject、removeObject 前后打印日志(如 key、耗时、是否命中)。它甚至不碰 delegate 的内部结构,纯粹包装调用过程。
立即学习“Java免费学习笔记(深入)”;
- 不改变行为,只增加可观测性,调试缓存问题时非常直观
- 开启方式简单:
<cache/>默认不启用;需配合全局 setting:<setting name="logImpl" value="SLF4J"/>,且 LoggingCache 会在 setStandardDecorators 中按需注入 - 生产环境慎用,高频查询下日志 I/O 可能成为瓶颈
装饰链是动态组装的,不是硬编码的
MyBatis 在创建二级缓存实例时,会根据 XML 或注解配置,按固定顺序调用 setStandardDecorators 方法。例如配置了 flushInterval="60000" 和 readOnly="true",就会依次套上 ScheduledCache → SerializedCache → LoggingCache → PerpetualCache。这个链条可以增删,但顺序有讲究:比如 ScheduledCache 必须在外层,才能保证定时 clear 作用于整个链;而 SerializedCache 必须在 LoggingCache 内侧,否则日志里打出来的就是字节数组。


















