MyBatis--缓存详解
一、 一级缓存(Local Cache)
1.1 概念
作用域:
SqlSession
级别(默认开启,不能关闭)。原理:一次查询结果会放到当前 SqlSession 的
HashMap
中,后续相同 SQL(相同参数、相同 StatementId)会直接从缓存取,而不会再查数据库。失效场景:
SqlSession 关闭。
SqlSession 执行了
INSERT
、UPDATE
、DELETE
操作(缓存会清空)。查询参数不同或 SQL 不同。
手动调用
clearCache()
。👉 一级缓存属于 会话缓存,在 Spring Boot 中,由于每次调用 Mapper 方法默认都会使用新的 SqlSession,所以在事务范围内能体现。
二、二级缓存(Global Cache)
2.1 概念
作用域:
SqlSessionFactory
级别(即 Mapper namespace 级别),可被多个 SqlSession 共享。默认情况:全局关闭,需要手动配置。
原理:查询结果会被序列化后存储在二级缓存区域(内存或 Redis 等),不同 SqlSession 之间共享缓存。
失效场景:
执行增删改操作时,清空当前 namespace 下的缓存。
缓存达到最大 size 或超时。
👉 二级缓存适合读多写少的场景。分布式系统中常用 Redis 代替内存缓存。
三、 一级缓存示例(Spring Boot 默认行为)
UserMapper.java
@Mapper public interface UserMapper {User findById(Integer id); }
UserMapper.xml
<mapper namespace="com.example.mapper.UserMapper"><select id="findById" parameterType="int" resultType="com.example.entity.User">SELECT * FROM user WHERE id = #{id}</select> </mapper>
User 实体类
@Data public class User implements Serializable {private Integer id;private String username;private Integer age; }
测试(验证一级缓存)
@SpringBootTest class MybatisCacheTest {@Autowiredprivate UserMapper userMapper;@Autowiredprivate SqlSessionFactory sqlSessionFactory;@Testvoid testFirstLevelCache() {// 获取 SqlSession,手动控制事务,保证在同一个 SqlSession 中try (SqlSession session = sqlSessionFactory.openSession()) {UserMapper mapper = session.getMapper(UserMapper.class);// 第一次查询 -> 查数据库User u1 = mapper.findById(1);System.out.println(u1);// 第二次查询 -> 命中一级缓存,不走数据库User u2 = mapper.findById(1);System.out.println(u2);// 执行插入 -> 会清空缓存mapper.insert(new User(null, "Tom", 20));// 第三次查询 -> 重新查数据库User u3 = mapper.findById(1);System.out.println(u3);}} }
👉 在 同一个 SqlSession 内,多次执行相同查询会命中 一级缓存。
四、 二级缓存示例(Spring Boot 手动开启)
4.1 (本地 JVM 缓存):
application.yml
mybatis:configuration:cache-enabled: true # 全局开启二级缓存mapper-locations: classpath:mapper/*.xml
UserMapper.xml(启用二级缓存JVM)
<mapper namespace="com.example.mapper.UserMapper"><!-- 开启二级缓存 --><cache eviction="LRU" flushInterval="60000" size="512" readOnly="false"/><select id="findById" parameterType="int" resultType="com.example.entity.User">SELECT * FROM user WHERE id = #{id}</select> </mapper>
测试(验证二级缓存)
@SpringBootTest class MybatisCacheTest {@Autowiredprivate SqlSessionFactory sqlSessionFactory;@Testvoid testSecondLevelCache() {// 第一次查询:走数据库,结果写入二级缓存try (SqlSession session1 = sqlSessionFactory.openSession()) {UserMapper mapper1 = session1.getMapper(UserMapper.class);User user1 = mapper1.findById(9);System.out.println("第一次查询:" + user1);session1.close(); // 一级缓存失效,但二级缓存仍保存}// 第二次查询:命中二级缓存(不查数据库)try (SqlSession session2 = sqlSessionFactory.openSession()) {UserMapper mapper2 = session2.getMapper(UserMapper.class);User user2 = mapper2.findById(9);System.out.println("第二次查询:" + user2);session2.close();}} }
👉 在不同的 SqlSession 中也能复用缓存,因为二级缓存存在于 SqlSessionFactory 范围。
4.2 (不使用本地 JVM 缓存,而是使用 Redis):
引入依赖
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0</version> </dependency>
application.yml
spring:datasource:url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTCusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Driverredis:host: 127.0.0.1port: 6379
UserMapper.xml(启用二级缓存redis)
<mapper namespace="com.example.mapper.UserMapper"><cache type="org.mybatis.caches.redis.RedisCache"/><select id="findById" parameterType="int" resultType="com.example.entity.User">SELECT * FROM user WHERE id = #{id}</select> </mapper>
测试(验证二级缓存)
@SpringBootTest public class MybatisRedisCacheTest {@Autowiredprivate SqlSessionFactory sqlSessionFactory;@Testpublic void testRedisCache() {try (SqlSession session1 = sqlSessionFactory.openSession();SqlSession session2 = sqlSessionFactory.openSession()) {UserMapper mapper1 = session1.getMapper(UserMapper.class);System.out.println(mapper1.findById(1));session1.close(); // 数据进入 RedisUserMapper mapper2 = session2.getMapper(UserMapper.class);System.out.println(mapper2.findById(1)); // 直接从 Redis 命中}} }
4.3使用本地 JVM 缓存和Redis的对比
4.3.1 原生的二级缓存(没有 Redis 的时候)
存储位置:二级缓存是基于 Mapper(namespace)级别 的,存储在 当前应用 JVM 的内存 里(默认实现是
PerpetualCache
+HashMap
)。作用范围:同一个 Mapper 下,多个 SqlSession 之间可以共享缓存。
生命周期:缓存随着应用 JVM 存在,当应用重启时缓存会消失。
特点:
只能在 单机应用 下使用;
不能跨服务共享数据;
容量受限于 JVM 内存;
适合一些 读多写少的小型项目。
💡 举例:
应用 A 部署在服务器 X,查询了UserMapper.findById(1)
,MyBatis 会把数据放到本地 JVM 内存的二级缓存中。
下次再查
findById(1)
(即使是新的 SqlSession),也可以直接从本地缓存拿到结果;但是如果还有一个应用 B(另一个服务实例),它就完全拿不到这个缓存。
👉 所以原生二级缓存本质上是“单机的 Mapper 级缓存”。
4.3.2 结合 Redis 的二级缓存(分布式)
存储位置:MyBatis 不再把缓存放在 JVM 内存,而是存放到 Redis 这样的分布式缓存 里。
作用范围:不同服务实例(多个 JVM)之间都能共享缓存。
生命周期:缓存由 Redis 管理,可以持久化或设置 TTL(过期时间)。
特点:
支持 多机共享缓存,解决了单机缓存不一致的问题;
Redis 提供了更大的存储容量和高可用机制;
适合 分布式集群环境。
💡 举例:
应用 A 部署在服务器 X,应用 B 部署在服务器 Y。
A 查询
UserMapper.findById(1)
,结果会缓存到 Redis;B 查询
UserMapper.findById(1)
,即使它的 JVM 里没缓存,也能从 Redis 里拿到数据。👉 所以结合 Redis 后,二级缓存从“单机 JVM 缓存”变成了“分布式共享缓存”。
4.3.3 对比总结
特性 原生二级缓存(默认) 分布式二级缓存(结合 Redis) 存储位置 本地 JVM 内存 Redis(独立缓存服务器) 作用范围 单个应用实例 多个应用实例共享 生命周期 应用关闭即失效 Redis 可持久化 / TTL 控制 并发支持 单机缓存,受 JVM 限制 高并发支持,Redis 分布式存储 使用场景 小型单体应用 分布式 / 微服务架构
✅ 结论:
以前(默认二级缓存) → 只是单机 JVM 内存里的
namespace
级缓存;现在(结合 Redis) → 变成了真正的“分布式共享缓存”,跨服务可见,适合 Spring Boot + 微服务架构。
五、 总结对比
特性 一级缓存(Local Cache) 二级缓存(Global Cache) 作用域 SqlSession SqlSessionFactory / namespace 默认开启 ✅ 是 ❌ 否,需要配置 生命周期 SqlSession 存活期间 SqlSessionFactory 存活期间 是否共享 仅限同一 SqlSession 内 跨 SqlSession 共享 失效场景 SqlSession 关闭、增删改、手动 clearCache 增删改、超时、size 达上限 常见用途 单次事务内减少重复查询 跨会话共享查询结果,提高性能