二级缓存配置
MyBatis 二级缓存是 Mapper(namespace)级别的缓存,多个 SqlSession 可以共享同一份缓存数据。通过合理配置可以实现跨 Session 的数据共享与缓存管理。
一级与二级缓存对比
| 特性 | 一级缓存 | 二级缓存 |
|---|---|---|
| 作用域 | SqlSession 级别 | Mapper(namespace)级别 |
| 共享性 | 不跨 Session | 多个 Session 共享 |
| 默认状态 | 默认开启 | 默认关闭 |
| 存储结构 | PerpetualCache(HashMap) | 可配置(默认 PerpetualCache) |
| 失效条件 | 写操作后自动清空 | 需手动配置 flushInterval 等 |
| 适用场景 | 单 Session 内重复查询 | 多 Session 间数据共享查询 |
开启二级缓存
1. 全局配置
首先在 mybatis-config.xml 中开启二级缓存支持:
XML
<!-- mybatis-config.xml -->
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
cacheEnabled默认值为true,通常无需修改。设为false时将禁用所有二级缓存。
2. Mapper 级别开启
在对应的 Mapper XML 文件中添加 <cache/> 标签:
XML
<!-- UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 开启二级缓存 -->
<cache/>
<select id="selectById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
3. 实体类实现序列化
二级缓存要求缓存的对象实现 Serializable 接口:
Java
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String email;
// getters and setters
}
cache 标签属性详解
<cache/> 标签支持多个配置属性:
XML
<cache
eviction="LRU"
flushInterval="60000"
size="1024"
readOnly="false"
type="org.apache.ibatis.cache.impl.PerpetualCache"/>
| 属性 | 默认值 | 说明 |
|---|---|---|
eviction | LRU | 缓存回收策略(LRU/FIFO/SOFT/WEAK) |
flushInterval | 无 | 缓存刷新间隔(毫秒),不设时只在写操作时刷新 |
size | 1024 | 缓存最大存储对象数 |
readOnly | false | 是否为只读缓存 |
type | PerpetualCache | 自定义缓存实现类 |
eviction 回收策略
MyBatis 内置四种缓存回收策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| LRU | 最近最少使用(Least Recently Used),淘汰最久未使用的缓存项 | 通用场景,推荐首选 |
| FIFO | 先进先出(First In First Out),按进入顺序淘汰 | 数据具有时效性 |
| SOFT | 软引用(Soft Reference),基于 JVM 垃圾回收机制 | 内存敏感,大缓存 |
| WEAK | 弱引用(Weak Reference),更积极地基于垃圾回收 | 极内存敏感场景 |
LRU 策略
XML
<!-- 默认策略,可省略 eviction 属性 -->
<cache eviction="LRU" size="512"/>
LRU 算法会记录每个缓存项的使用时间,当缓存满时淘汰最久没有被访问的项。
FIFO 策略
XML
<cache eviction="FIFO" size="512"/>
FIFO 按缓存项进入队列的顺序淘汰,先进入的先被淘汰,不关心是否被访问过。
SOFT / WEAK 引用策略
XML
<cache eviction="SOFT" size="1024"/>
SOFT 和 WEAK 基于 JVM 的引用类型:
- SOFT:JVM 在内存不足时才回收缓存项
- WEAK:JVM 在每次 GC 时都可能回收缓存项
flushInterval 刷新间隔
XML
<!-- 每 60 秒自动刷新缓存 -->
<cache flushInterval="60000"/>
| 配置方式 | 行为 |
|---|---|
| 不设置 | 只在 INSERT/UPDATE/DELETE 时刷新缓存 |
| 设置毫秒值 | 每隔指定时间自动清空缓存 |
不设置 flushInterval
XML
<cache/>
缓存只在写操作时刷新,适合读多写少的场景。
设置固定间隔
XML
<!-- 每 30 秒刷新一次 -->
<cache flushInterval="30000" size="256"/>
<!-- 每 5 分钟刷新一次 -->
<cache flushInterval="300000" size="512"/>
flushInterval 设置得越短,缓存命中率越低;设置得越长,数据实时性越差。
size 容量配置
XML
<cache size="1024"/>
| 场景 | 建议 size | 说明 |
|---|---|---|
| 小数据量 | 256-512 | 数据表行数少,查询条件单一 |
| 中等数据量 | 512-1024 | 通用场景,平衡内存与命中率 |
| 大数据量 | 1024-4096 | 查询条件多,缓存项大 |
size 不是绝对上限,受 eviction 策略影响。LRU/FIFO 会严格限制,SOFT/WEAK 依赖 JVM GC。
readOnly 只读配置
XML
<!-- 只读缓存,返回缓存对象的引用 -->
<cache readOnly="true"/>
<!-- 非只读(默认),返回缓存对象的拷贝 -->
<cache readOnly="false"/>
| 模式 | 返回值 | 性能 | 安全性 |
|---|---|---|---|
| readOnly=true | 原对象引用 | 高(无拷贝开销) | 低(可能被修改) |
| readOnly=false | 序列化拷贝 | 低(有序列化开销) | 高(隔离修改) |
readOnly 与序列化密切相关,详见下一篇文章《缓存读写策略》。
使用 useCache 和 flushCache 控制缓存
useCache 属性
控制单个 SQL 语句是否使用二级缓存:
XML
<!-- 默认使用二级缓存 -->
<select id="selectById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 不使用二级缓存 -->
<select id="selectById" resultType="User" useCache="false">
SELECT * FROM user WHERE id = #{id}
</select>
| useCache | 行为 | 适用场景 |
|---|---|---|
| true(默认) | 使用二级缓存 | 一般查询 |
| false | 不使用二级缓存 | 实时查询、统计类 SQL |
flushCache 属性
控制 SQL 执行后是否刷新二级缓存:
XML
<!-- 执行后刷新二级缓存 -->
<insert id="insertUser" flushCache="true">
INSERT INTO user (username, email) VALUES (#{username}, #{email})
</insert>
<select id="selectById" flushCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
| flushCache | 行为 | 适用场景 |
|---|---|---|
| true | 执行后清空二级缓存 | INSERT/UPDATE/DELETE(默认) |
| false | 不清空二级缓存 | SELECT(默认) |
完整配置示例
XML
<!-- UserMapper.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.UserMapper">
<!-- 完整二级缓存配置 -->
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="false"/>
<!-- 使用缓存的查询 -->
<select id="selectById" resultType="User" useCache="true">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 不使用缓存的查询 -->
<select id="selectRealTime" resultType="User" useCache="false">
SELECT * FROM user WHERE id = #{id}
</select>
<!-- 写操作自动刷新缓存 -->
<update id="updateUser">
UPDATE user SET username = #{username} WHERE id = #{id}
</update>
</mapper>
二级缓存命中条件
二级缓存的命中条件比一级缓存更严格:
- 相同的 Mapper 命名空间:
namespace一致 - 相同的 SQL 语句:
MappedStatement.id一致 - 相同的参数值:参数值完全相等
- 相同的 RowBounds:分页条件一致
- 写操作后未被刷新:INSERT/UPDATE/DELETE 或 flushCache=true
- 未超过 flushInterval 间隔:缓存未超时
Java
SqlSessionFactory factory = sqlSessionFactory;
// Session 1
SqlSession session1 = factory.openSession();
UserMapper mapper1 = session1.getMapper(UserMapper.class);
User user1 = mapper1.selectById(1); // 访问数据库,写入二级缓存
session1.commit();
session1.close();
// Session 2(不同 Session)
SqlSession session2 = factory.openSession();
UserMapper mapper2 = session2.getMapper(UserMapper.class);
User user2 = mapper2.selectById(1); // 命中二级缓存
System.out.println(user2);
session2.close();
注意事项
- 实体类必须实现 Serializable:二级缓存需要对对象进行序列化
- 缓存隔离级别:二级缓存不是线程安全的,多实例环境下可能出问题
- 脏读风险:多表关联更新时,只清空当前 Mapper 的缓存可能导致脏数据
- flushInterval 与 size:间隔太短导致命中率低,size 太小导致频繁淘汰
- 分布式环境:二级缓存是本地缓存,多节点不共享,需使用第三方缓存
要点总结
- 二级缓存是 Mapper(namespace)级别,多个 SqlSession 共享
- 开启步骤:全局
cacheEnabled=true→ Mapper XML 添加<cache/>→ 实体实现 Serializable - 四种 eviction 策略:LRU(推荐)、FIFO、SOFT、WEAK
- flushInterval 控制自动刷新间隔,size 控制缓存最大容量
- readOnly 决定返回引用还是拷贝,影响性能与安全性
- 单条 SQL 可通过 useCache 和 flushCache 精细控制缓存行为
- 二级缓存不适合分布式环境,多表关联更新可能导致脏数据
存放路径:D:\git2\jwdev\articles\MYBATIS\进阶\缓存机制\二级缓存配置.md
📝 发现内容有误?点击此处直接编辑