MyBatis--缓存详解

一、 一级缓存(Local Cache)

1.1 概念

  • 作用域SqlSession 级别(默认开启,不能关闭)。

  • 原理 :一次查询结果会放到当前 SqlSession 的 HashMap 中,后续相同 SQL(相同参数、相同 StatementId)会直接从缓存取,而不会再查数据库。

  • 失效场景

    1. SqlSession 关闭。

    2. SqlSession 执行了 INSERTUPDATEDELETE 操作(缓存会清空)。

    3. 查询参数不同或 SQL 不同。

    4. 手动调用 clearCache()

👉 一级缓存属于 会话缓存,在 Spring Boot 中,由于每次调用 Mapper 方法默认都会使用新的 SqlSession,所以在事务范围内能体现。


二、二级缓存(Global Cache)

2.1 概念

  • 作用域SqlSessionFactory 级别(即 Mapper namespace 级别),可被多个 SqlSession 共享。

  • 默认情况:全局关闭,需要手动配置。

  • 原理:查询结果会被序列化后存储在二级缓存区域(内存或 Redis 等),不同 SqlSession 之间共享缓存。

  • 失效场景

    1. 执行增删改操作时,清空当前 namespace 下的缓存。

    2. 缓存达到最大 size 或超时。

👉 二级缓存适合读多写少的场景。分布式系统中常用 Redis 代替内存缓存。


三、 一级缓存示例(Spring Boot 默认行为)

UserMapper.java

java 复制代码
@Mapper
public interface UserMapper {
    User findById(Integer id);
}

UserMapper.xml

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 实体类

java 复制代码
@Data
public class User implements Serializable {
    private Integer id;
    private String username;
    private Integer age;
}

测试(验证一级缓存)

java 复制代码
@SpringBootTest
class MybatisCacheTest {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Test
    void 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

java 复制代码
mybatis:
  configuration:
    cache-enabled: true   # 全局开启二级缓存
  mapper-locations: classpath:mapper/*.xml

UserMapper.xml(启用二级缓存JVM)

XML 复制代码
<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>

测试(验证二级缓存)

java 复制代码
@SpringBootTest
class MybatisCacheTest {

    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Test
    void 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**)**:

引入依赖

XML 复制代码
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-redis</artifactId>
    <version>1.0.0</version>
</dependency>

application.yml

java 复制代码
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver

  redis:
    host: 127.0.0.1
    port: 6379

UserMapper.xml(启用二级缓存redis)

XML 复制代码
<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>

测试(验证二级缓存)

java 复制代码
@SpringBootTest
public class MybatisRedisCacheTest {
    @Autowired
    private SqlSessionFactory sqlSessionFactory;

    @Test
    public 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(); // 数据进入 Redis

            UserMapper 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 达上限
常见用途 单次事务内减少重复查询 跨会话共享查询结果,提高性能
相关推荐
Spider Cat 蜘蛛猫14 小时前
Springboot SSO系统设计文档
java·spring boot·后端
月落归舟14 小时前
一篇文章了解Redis内存淘汰机制与过期Key清理
数据库·redis·mybatis
phltxy15 小时前
Redis 事务
数据库·redis·缓存
1892280486117 小时前
NV265固态MT29F32T08GSLBHL8-24QMES:B
大数据·服务器·人工智能·科技·缓存
bksczm20 小时前
缓存区细则
缓存
接着奏乐接着舞21 小时前
java 数据结构
数据库·redis·缓存
许长安21 小时前
Redis 跳表实现详解
数据库·c++·经验分享·redis·笔记·缓存
桂花很香,旭很美1 天前
Redis-智能体开发中的大杀器
数据库·redis·缓存
xkxnq1 天前
第七阶段:企业级项目实战核心能力(118天)Vue项目缓存策略:接口缓存(内存+本地)+ 组件缓存+路由缓存组合方案
vue.js·spring·缓存
学习3人组1 天前
业务主表+JSON自定义字段
java·spring boot·json