MyBatis 提供了两级缓存机制:一级缓存(Local Cache) 和 二级缓存(Global Cache),用于提升数据库查询性能。合理使用缓存可以显著减少数据库访问次数,提升系统响应速度。
一、核心概念
1. 一级缓存(Local Cache / Session Cache)
- 作用范围:
SqlSession
级别。 - 生命周期:与
SqlSession
绑定,SqlSession
关闭或清空后缓存失效。 - 默认开启:无需配置,默认开启。
- 存储位置:内存中,属于当前
SqlSession
的本地缓存。 - 命中条件:
- 同一个
SqlSession
- 相同的 SQL 语句
- 相同的参数
- 相同的
RowBounds
- 相同的
Statement ID
- 同一个
✅ 示例:同一个
SqlSession
中执行两次selectById(1)
,第二次直接从缓存返回。
2. 二级缓存(Global Cache / Mapper Cache)
- 作用范围:
namespace
级别(即一个Mapper
接口对应一个缓存)。 - 生命周期:跨
SqlSession
,应用级缓存。 - 默认关闭:需手动开启。
- 存储位置:可配置为内存(如
PerpetualCache
)、Redis、Ehcache 等。 - 命中条件:
- 不同的
SqlSession
- 相同的
namespace
(Mapper) - 相同的 SQL 和参数
- 数据未被
INSERT
、UPDATE
、DELETE
操作清空
- 不同的
✅ 示例:
SqlSession1
查询UserMapper.selectById(1)
,SqlSession2
再次查询,可从二级缓存命中。
二、操作步骤(非常详细)
步骤 1:准备环境
确保项目中已集成 MyBatis 或 MyBatis-Plus。
<!-- Maven 依赖示例 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.13</version>
</dependency>
步骤 2:启用二级缓存(全局配置)
在 mybatis-config.xml
或 application.yml
中开启二级缓存。
方式一:XML 配置(mybatis-config.xml
)
<configuration>
<settings>
<!-- 开启二级缓存 -->
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
方式二:Spring Boot 配置(application.yml
)
mybatis:
configuration:
cache-enabled: true
⚠️ 注意:
cacheEnabled
默认为true
,但建议显式声明。
步骤 3:在 Mapper XML 中启用缓存
在需要缓存的 Mapper.xml
文件中添加 <cache />
标签。
<?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>
<update id="update" parameterType="User">
UPDATE user SET name = #{name} WHERE id = #{id}
</update>
</mapper>
步骤 4:实体类实现 Serializable
二级缓存会序列化对象,因此实体类必须实现 Serializable
。
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String name;
private Integer age;
// getter/setter
}
步骤 5:测试一级缓存
@Test
public void testFirstLevelCache() {
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 第一次查询,访问数据库
User user1 = mapper.selectById(1L);
System.out.println("第一次查询: " + user1);
// 第二次查询,命中一级缓存(不访问数据库)
User user2 = mapper.selectById(1L);
System.out.println("第二次查询: " + user2);
// 清空缓存或关闭 SqlSession 后缓存失效
sqlSession.clearCache(); // 手动清空
// 再次查询,重新访问数据库
User user3 = mapper.selectById(1L);
System.out.println("清空后查询: " + user3);
sqlSession.close();
}
步骤 6:测试二级缓存
@Test
public void testSecondLevelCache() {
// SqlSession1
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = mapper1.selectById(1L);
System.out.println("SqlSession1 查询: " + user1);
sqlSession1.close(); // 关闭后,结果写入二级缓存
// SqlSession2
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
User user2 = mapper2.selectById(1L);
System.out.println("SqlSession2 查询: " + user2); // 应从二级缓存读取
sqlSession2.close();
}
三、常见错误与解决方案
错误现象 | 原因 | 解决方案 |
---|---|---|
二级缓存未生效 | 未添加 <cache/> 标签 |
在 Mapper.xml 中添加 <cache /> |
抛出 NotSerializableException |
实体类未实现 Serializable |
添加 implements Serializable |
更新后缓存未刷新 | 缓存未自动清空 | 确保 INSERT 、UPDATE 、DELETE 会自动清空缓存 |
跨 SqlSession 未共享缓存 |
多个 SqlSession 使用不同 namespace |
确保 namespace 一致 |
缓存数据过期或丢失 | 缓存实现未配置过期策略 | 使用 Redis/Ehcache 并配置 TTL |
四、注意事项
缓存清空时机:
- 任何
INSERT
、UPDATE
、DELETE
操作都会清空当前namespace
的二级缓存。 sqlSession.clearCache()
清空一级缓存。
- 任何
缓存粒度:
- 二级缓存是
namespace
级别的,不能按方法粒度控制。
- 二级缓存是
事务影响:
- 在事务中,缓存行为受事务隔离级别影响。提交事务后缓存才可能被其他
SqlSession
访问。
- 在事务中,缓存行为受事务隔离级别影响。提交事务后缓存才可能被其他
MyBatis-Plus 兼容性:
- MP 完全兼容 MyBatis 缓存机制。
- 使用
BaseMapper
方法(如selectById
)时,缓存同样生效。
五、使用技巧
1. 自定义缓存配置
<cache
type="org.mybatis.caches.redis.RedisCache"
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="false"/>
type
:缓存实现类(如 Redis、Ehcache)eviction
:回收策略(LRU、FIFO、SOFT、WEAK)flushInterval
:刷新间隔(毫秒)size
:最多缓存对象数readOnly
:是否只读(true
为只读,性能更高)
2. 禁用某个查询的缓存
<select id="selectFreshData" resultType="User" useCache="false">
SELECT * FROM user WHERE status = 1
</select>
useCache="false"
表示该查询不参与二级缓存。
3. 刷新缓存
<update id="updateUser" parameterType="User" flushCache="true">
UPDATE user SET name = #{name} WHERE id = #{id}
</update>
flushCache="true"
强制清空缓存(默认 INSERT
/UPDATE
/DELETE
已为 true
)。
六、最佳实践与性能优化
✅ 最佳实践
实践 | 说明 |
---|---|
合理使用一级缓存 | 在单个业务方法中,避免重复查询相同数据 |
谨慎使用二级缓存 | 适用于读多写少、数据变化不频繁的场景(如字典表) |
避免缓存雪崩 | 设置随机过期时间,避免大量缓存同时失效 |
监控缓存命中率 | 通过日志或监控工具观察缓存效率 |
敏感数据不缓存 | 用户权限、订单状态等频繁变更数据慎用缓存 |
⚡ 性能优化建议
选择高效缓存实现:
- 生产环境推荐使用
Redis
或Ehcache
替代默认PerpetualCache
。
- 生产环境推荐使用
控制缓存大小:
- 避免缓存过多数据导致内存溢出。
使用只读缓存:
- 如果对象不会被修改,设置
readOnly="true"
,避免序列化开销。
- 如果对象不会被修改,设置
避免大结果集缓存:
- 分页查询结果可缓存,但全表查询不建议缓存。
结合本地缓存 + 分布式缓存:
- 一级缓存(本地) + 二级缓存(Redis),形成多级缓存架构。
七、总结对比
特性 | 一级缓存 | 二级缓存 |
---|---|---|
作用范围 | SqlSession |
namespace (Mapper) |
默认开启 | 是 | 否(需配置) |
跨会话共享 | 否 | 是 |
存储位置 | JVM 内存 | 可配置(内存、Redis等) |
清空时机 | SqlSession 关闭/清空 |
DML 操作、手动清空 |
适用场景 | 单次请求内重复查询 | 跨请求、跨会话的热点数据 |
八、常见问题 FAQ
Q1:MyBatis-Plus 是否支持缓存?
A:完全支持。MP 基于 MyBatis,缓存机制一致。
Q2:为什么 update
后缓存没有更新?
A:MyBatis 是清空缓存而非“更新”,下次查询会重新加载。
Q3:如何禁用一级缓存?
A:不推荐。可通过 sqlSession.clearCache()
手动控制,或使用 REUSE
执行器(不常用)。
Q4:缓存穿透怎么办?
A:可缓存 null
值(短时间),或使用布隆过滤器预判。