戎玉珂 发表于 2025-12-18 20:50:03

深入理解MyBatis缓存机制:一二级缓存全解析

引言

在现代Web应用中,数据库访问往往是性能瓶颈之一。MyBatis作为流行的持久层框架,其缓存机制是提升应用性能的关键特性。理解MyBatis的一二级缓存不仅有助于优化应用性能,还能避免因缓存不当导致的数据一致性问题。本文将从基础概念到高级原理,全方位解析MyBatis缓存机制。
一、缓存的基本概念:为什么需要缓存?

1.1 缓存的价值

想象一下,如果你每次需要知道时间都去天文台查询,效率会很低。相反,看一眼手表(缓存)就能立即获取时间。MyBatis缓存扮演的就是这个“手表”的角色,它避免了频繁访问数据库(天文台),极大提升了查询效率。
1.2 缓存的经济学原理


[*]时间局部性:刚被访问的数据很可能再次被访问
[*]空间局部性:相邻的数据很可能被一起访问
[*]访问成本:内存访问(纳秒级)vs 磁盘/网络访问(毫秒级)
二、一级缓存:SqlSession级别的缓存

2.1 什么是SqlSession?

在深入一级缓存前,需要先理解SqlSession。SqlSession不是数据库连接(Connection),而是一次数据库对话的抽象:
// SqlSession相当于一次完整对话,不是一通电话
SqlSession session = sqlSessionFactory.openSession();
try {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 对话中的多次查询
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>userMapper.getUser(1);<cache size="1024"/><cache flushInterval="1800000"/>// 第一次查询
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>orderMapper.getOrders(1);<cache size="1024"/><cache flushInterval="1800000"/>// 第二次查询
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>accountMapper.getBalance(1);<cache size="1024"/><cache flushInterval="1800000"/>// 第三次查询
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>session.commit();<cache size="1024"/><cache flushInterval="1800000"/>// 确认对话内容
} finally {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>session.close();<cache size="1024"/><cache flushInterval="1800000"/>// 结束对话
}2.2 一级缓存的核心特性

作用范围:SqlSession内部(一次对话)
默认状态:自动开启,无法关闭
生命周期:随SqlSession创建而创建,随其关闭而销毁
2.3 一级缓存的工作原理

// 示例代码展示一级缓存行为
public void demonstrateLevel1Cache() {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>SqlSession session = sqlSessionFactory.openSession();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>UserMapper mapper = session.getMapper(UserMapper.class);
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>System.out.println("第一次查询用户1:");
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>User user1 = mapper.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/>// 发SQL:SELECT * FROM user WHERE id=1
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>System.out.println("第二次查询用户1:");
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>User user2 = mapper.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/>// 不发SQL!从一级缓存读取
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>System.out.println("查询用户2:");
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>User user3 = mapper.selectById(2);<cache size="1024"/><cache flushInterval="1800000"/>// 发SQL:参数不同,缓存未命中
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>System.out.println("修改用户1:");
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>mapper.updateUser(user1);<cache size="1024"/><cache flushInterval="1800000"/>// 清空一级缓存
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>System.out.println("再次查询用户1:");
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>User user4 = mapper.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/>// 发SQL:缓存被清空
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>session.close();
}2.4 一级缓存的数据结构

一级缓存的实现非常简单直接:
// 一级缓存的核心实现类
public class PerpetualCache implements Cache {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 核心:就是一个ConcurrentHashMap!
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>private final Map<Object, Object> cache = new ConcurrentHashMap<>();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>@Override
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>public void putObject(Object key, Object value) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>cache.put(key, value);<cache size="1024"/><cache flushInterval="1800000"/>// 简单的Map.put()
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>@Override
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>public Object getObject(Object key) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>return cache.get(key);<cache size="1024"/><cache flushInterval="1800000"/>// 简单的Map.get()
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}
}缓存Key的生成规则:
// CacheKey包含以下要素,决定两个查询是否"相同"
// 1. Mapper Id(namespace + method)
// 2. 分页参数(offset, limit)
// 3. SQL语句
// 4. 参数值
// 5. 环境Id

// 这意味着:即使SQL相同,参数不同,也会生成不同的CacheKey2.5 一级缓存的失效场景


[*]执行任何UPDATE/INSERT/DELETE操作
[*]手动调用clearCache()
[*]设置flushCache="true"
[*]SqlSession关闭
[*]查询参数变化(因为CacheKey不同)
三、二级缓存:Mapper级别的全局缓存

3.1 二级缓存的核心特性

作用范围:Mapper级别(跨SqlSession共享)
默认状态:默认关闭,需要手动开启
生命周期:随应用运行而存在
3.2 二级缓存的配置

<settings>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><setting name="cacheEnabled" value="true"/>
</settings>


<mapper namespace="com.example.UserMapper">
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>eviction="LRU"<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>   
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>flushInterval="60000"<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>size="1024"<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>readOnly="true"<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>blocking="false"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>   
</mapper>


<select id="selectById" resultType="User" useCache="true">
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>SELECT * FROM user WHERE id = #{id}
</select>


<update id="updateUser" flushCache="true">
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>UPDATE user SET name = #{name} WHERE id = #{id}
</update>3.3 二级缓存的数据结构

二级缓存不像一级缓存那么简单,它采用了装饰器模式:
二级缓存装饰器链(层层包装):
┌─────────────────────────┐
│<cache size="1024"/><cache flushInterval="1800000"/>SerializedCache<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>│ ← 序列化存储
│<cache size="1024"/><cache flushInterval="1800000"/>LoggingCache<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>   │ ← 日志统计
│<cache size="1024"/><cache flushInterval="1800000"/>SynchronizedCache<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>│ ← 线程安全
│<cache size="1024"/><cache flushInterval="1800000"/>LruCache<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>   │ ← LRU淘汰
│<cache size="1024"/><cache flushInterval="1800000"/>PerpetualCache<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>   │ ← 基础HashMap
└─────────────────────────┘每个装饰器都有特定功能:

[*]PerpetualCache:基础存储,使用HashMap
[*]LruCache:最近最少使用淘汰
[*]SynchronizedCache:保证线程安全
[*]LoggingCache:记录命中率
[*]SerializedCache:序列化对象,防止修改
3.4 二级缓存的工作流程

public void demonstrateLevel2Cache() {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 用户A查询(第一个访问者)
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>SqlSession sessionA = sqlSessionFactory.openSession();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>UserMapper mapperA = sessionA.getMapper(UserMapper.class);
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>User user1 = mapperA.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/>// 查询数据库
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>sessionA.close();<cache size="1024"/><cache flushInterval="1800000"/>// 关键:关闭时才会写入二级缓存
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 用户B查询(不同SqlSession)
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>SqlSession sessionB = sqlSessionFactory.openSession();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>UserMapper mapperB = sessionB.getMapper(UserMapper.class);
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>User user2 = mapperB.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/>// 从二级缓存读取,不发SQL
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 管理员更新数据
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>SqlSession sessionC = sqlSessionFactory.openSession();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>UserMapper mapperC = sessionC.getMapper(UserMapper.class);
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>mapperC.updateUser(user1);<cache size="1024"/><cache flushInterval="1800000"/>// 清空相关二级缓存
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>sessionC.commit();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>sessionC.close();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 用户D再次查询
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>SqlSession sessionD = sqlSessionFactory.openSession();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>UserMapper mapperD = sessionD.getMapper(UserMapper.class);
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>User user3 = mapperD.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/>// 缓存被清,重新查询数据库
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>sessionD.close();
}3.5 二级缓存的同步机制

二级缓存有一个重要特性:事务提交后才更新。这意味着:
// 场景:事务内查询,事务提交前其他会话看不到更新
SqlSession session1 = sqlSessionFactory.openSession();
UserMapper mapper1 = session1.getMapper(UserMapper.class);

// 修改数据,但未提交
mapper1.updateUser(user);
// 此时二级缓存还未更新

// 另一个会话查询
SqlSession session2 = sqlSessionFactory.openSession();
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectById(1);<cache size="1024"/><cache flushInterval="1800000"/>// 可能读到旧数据!

session1.commit();<cache size="1024"/><cache flushInterval="1800000"/>// 提交后,二级缓存才会更新
// 之后的新查询才会看到新数据四、一二级缓存的对比与选择

4.1 核心差异对比

特性一级缓存二级缓存作用范围SqlSession内部Mapper级别,跨SqlSession默认状态开启关闭数据结构简单HashMap装饰器链共享性私有,不共享公共,所有会话共享生命周期随SqlSession创建销毁随应用运行持久存在性能影响极小(内存访问)中等(可能有序列化开销)适用场景会话内重复查询跨会话共享查询4.2 生活化比喻

一级缓存 = 私人对话记忆

[*]你和朋友的聊天内容,只有你们两人知道
[*]聊天结束(SqlSession关闭),记忆逐渐模糊
二级缓存 = 公司公告栏

[*]重要通知写在公告栏,所有员工都能看到
[*]通知更新时,需要擦掉旧的,写上新的
[*]公告栏内容持久存在,直到被更新
4.3 使用场景建议

适合一级缓存的场景:

// 场景1:方法内多次查询相同数据
public void processOrder(Long orderId) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>Order order1 = validateOrder(orderId);<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 第一次查数据库
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>Order order2 = calculateDiscount(orderId);<cache size="1024"/><cache flushInterval="1800000"/>// 走一级缓存
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>Order order3 = generateInvoice(orderId);<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 走一级缓存
}

// 场景2:循环内查询
for (int i = 0; i < 100; i++) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>Config config = configMapper.getConfig("system_timeout");
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 只有第一次查数据库,后续99次走缓存
}适合二级缓存的场景:

// 场景1:读多写少的配置数据
SystemConfig config = configMapper.getConfig("app_settings");
// 多个用户频繁读取,很少修改

// 场景2:热门商品信息
Product product = productMapper.getHotProduct(666);
// 商品详情页,大量用户访问同一商品

// 场景3:静态字典数据
List<City> cities = addressMapper.getAllCities();
// 城市列表,很少变化不适合缓存的场景:

// 场景1:实时性要求高的数据
Stock stock = stockMapper.getRealTimeStock(productId);
// 库存信息,需要实时准确

// 场景2:频繁更新的数据
UserBalance balance = accountMapper.getBalance(userId);
// 用户余额,每次交易都变化

// 场景3:大数据量查询
List<Log> logs = logMapper.getTodayLogs();
// 数据量大,缓存占用内存过多五、缓存的高级特性与原理

5.1 缓存淘汰策略

MyBatis提供了多种淘汰策略:
可用策略:

[*]LRU(Least Recently Used):最近最少使用(默认)
[*]FIFO(First In First Out):先进先出
[*]SOFT:软引用,内存不足时被GC回收
[*]WEAK:弱引用,GC时立即回收
5.2 LRU缓存的实现原理

public class LruCache implements Cache {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>private final Cache delegate;
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 使用LinkedHashMap实现LRU
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>private Map<Object, Object> keyMap;
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>private Object eldestKey;
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>public void setSize(final int size) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>@Override
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>boolean tooBig = size() > size;
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>if (tooBig) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>eldestKey = eldest.getKey();
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>return tooBig;
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>};
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>@Override
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>public Object getObject(Object key) {
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 访问时更新顺序
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>keyMap.get(key);
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>return delegate.getObject(key);
<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}
}5.3 缓存查询的完整流程

查询执行流程:
1. 请求到达CachingExecutor(二级缓存入口)
2. 生成CacheKey(包含SQL、参数等信息)
3. 查询二级缓存
<cache size="1024"/><cache flushInterval="1800000"/>   └─ 命中 → 返回结果
<cache size="1024"/><cache flushInterval="1800000"/>   └─ 未命中 → 继续
4. 查询一级缓存
<cache size="1024"/><cache flushInterval="1800000"/>   └─ 命中 → 返回结果,并放入二级缓存(事务提交时)
<cache size="1024"/><cache flushInterval="1800000"/>   └─ 未命中 → 继续
5. 查询数据库
6. 结果存入一级缓存
7. 事务提交时,一级缓存刷入二级缓存
8. 返回结果六、缓存的最佳实践与避坑指南

6.1 最佳实践

1. 合理配置缓存大小

<cache size="1024"/><cache flushInterval="1800000"/>2. 设置合理的刷新间隔

<cache size="1024"/><cache flushInterval="1800000"/>3. 选择性使用缓存

<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>SELECT * FROM realtime_table<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>SELECT * FROM important_table4. 关联查询的缓存策略

<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>6.2 常见问题与解决方案

问题1:脏读问题

场景:一个会话修改数据但未提交,另一个会话从二级缓存读取到旧数据。
解决方案:
// 设置事务隔离级别@Transactional(isolation = Isolation.READ_COMMITTED)public void updateUser(User user) {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>userMapper.updateUser(user);}// 或者在Mapper中设置flushCache@Update("UPDATE user SET name=#{name} WHERE id=#{id}")@Options(flushCache = Options.FlushCachePolicy.TRUE)int updateUser(User user);问题2:内存溢出

场景:缓存大量数据导致JVM内存不足。
解决方案:

[*]设置合理的缓存大小和淘汰策略
[*]使用软引用/弱引用缓存
[*]定期清理不活跃的缓存
问题3:分布式环境缓存不一致

场景:多台服务器,每台有自己的缓存,数据不一致。
解决方案:

[*]使用集中式缓存(Redis、Memcached)替代默认二级缓存
[*]实现自定义Cache接口:
public class RedisCache implements Cache {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>private JedisPool jedisPool;<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>@Override<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>public void putObject(Object key, Object value) {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>try (Jedis jedis = jedisPool.getResource()) {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>jedis.set(serialize(key), serialize(value));<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>@Override<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>public Object getObject(Object key) {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>try (Jedis jedis = jedisPool.getResource()) {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>byte[] value = jedis.get(serialize(key));<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>return deserialize(value);<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}}问题4:缓存穿透

场景:查询不存在的数据,每次都查数据库。
解决方案:
// 缓存空对象public User getUser(Long id) {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>User user = userMapper.selectById(id);<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>if (user == null) {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>// 缓存空值,设置短过期时间<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>cacheNullValue(id);<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>return null;<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>}<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>return user;}6.3 监控与调试

开启缓存日志

# 查看缓存命中情况
logging.level.org.mybatis=DEBUG
logging.level.com.example.mapper=TRACE监控缓存命中率

// 获取缓存统计信息Cache cache = sqlSession.getConfiguration()<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>.getCache("com.example.UserMapper");if (cache instanceof LoggingCache) {<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>LoggingCache loggingCache = (LoggingCache) cache;<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>System.out.println("命中次数: " + loggingCache.getHitCount());<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>System.out.println("未命中次数: " + loggingCache.getMissCount());<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>System.out.println("命中率: " +<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>   (loggingCache.getHitCount() * 100.0 /<cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/><cache size="1024"/><cache flushInterval="1800000"/>(loggingCache.getHitCount() + loggingCache.getMissCount())) + "%");}七、总结与思考

7.1 核心要点回顾


[*]一级缓存:SqlSession级别,自动开启,基于HashMap,简单高效
[*]二级缓存:Mapper级别,需手动开启,基于装饰器模式,功能丰富
[*]缓存Key:由SQL、参数等要素生成,决定查询是否"相同"
[*]事务同步:二级缓存在事务提交后才更新,避免脏读
[*]适用场景:根据数据特点选择合适的缓存策略
7.2 设计思想启示

MyBatis缓存设计体现了几个重要软件设计原则:

[*]单一职责原则:每个缓存装饰器只负责一个功能
[*]开闭原则:通过装饰器模式,无需修改原有代码即可扩展功能
[*]接口隔离:Cache接口定义清晰,便于自定义实现
7.3 实际应用建议

在实际项目中:

[*]从小开始:先使用一级缓存,确有需要再开启二级缓存
[*]测试验证:上线前充分测试缓存效果和内存占用
[*]监控调整:生产环境监控缓存命中率,根据实际情况调整配置
[*]文档记录:记录缓存配置和策略,便于团队协作和维护
7.4 未来展望

随着微服务和云原生架构的普及,MyBatis缓存也在演进:

[*]分布式缓存集成:更好支持Redis等分布式缓存
[*]多级缓存策略:本地缓存+分布式缓存的组合使用
[*]智能缓存管理:基于访问模式的自动缓存优化
结语

MyBatis缓存机制是一个看似简单实则精妙的设计。理解它不仅能帮助我们优化应用性能,还能加深对缓存设计模式的理解。记住,缓存是提升性能的利器,但也可能成为数据一致的陷阱。合理使用、谨慎配置、持续监控,才能让缓存真正为应用赋能。
缓存不是银弹,而是需要精心调校的利器。 在实际开发中,应根据业务特点、数据特性和访问模式,选择最合适的缓存策略,在性能与一致性之间找到最佳平衡点。

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

缢闸 发表于 2026-1-14 11:58:40

感谢分享,学习下。

福清婉 发表于 2026-1-18 06:42:06

yyds。多谢分享

趣侮 发表于 2026-1-19 04:44:57

感谢分享,下载保存了,貌似很强大

骆贵 发表于 2026-1-20 02:34:47

谢谢楼主提供!

郜庄静 发表于 2026-1-20 17:31:00

这个好,看起来很实用

嗳诿 发表于 2026-1-21 13:04:33

谢谢楼主提供!

扈季雅 发表于 2026-1-22 03:46:37

谢谢楼主提供!

咫噎 发表于 2026-1-24 10:36:02

谢谢分享,试用一下

怒鼓踊 发表于 2026-1-27 07:12:04

感谢分享,下载保存了,貌似很强大

裒噎 发表于 2026-1-28 02:30:20

谢谢楼主提供!

仁夹篇 发表于 2026-1-30 13:04:34

用心讨论,共获提升!

郏琼芳 发表于 2026-2-1 02:27:37

收藏一下   不知道什么时候能用到

愿隙 发表于 2026-2-1 02:28:35

谢谢楼主提供!

站竣凰 发表于 2026-2-1 05:29:10

用心讨论,共获提升!

国瑾瑶 发表于 2026-2-2 02:33:02

感谢发布原创作品,程序园因你更精彩

蔺堰 发表于 2026-2-4 10:11:09

感谢分享

鞍汉 发表于 2026-2-5 06:04:56

感谢分享,学习下。

迎脾 发表于 2026-2-6 23:41:56

谢谢分享,辛苦了

东郭欣然 发表于 2026-2-9 00:22:21

用心讨论,共获提升!
页: [1] 2
查看完整版本: 深入理解MyBatis缓存机制:一二级缓存全解析