2793 字
约 9 分钟
16
第09章 缓存机制
无标签

第09章 缓存机制

深入理解 MyBatis 的一级缓存和二级缓存机制
学习进度:36% 代码演示按钮 💻 查看完整代码 - 在线IDE体验

🎯 学习目标

📚

理解核心概念

掌握本章的核心概念和基本原理
🔧

掌握实践技能

学会相关的配置方法和实践技巧
💡

了解应用场景

明确在实际项目中的应用场景和最佳实践

学习目标

  • 深入理解MyBatis缓存机制的工作原理
  • 掌握一级缓存和二级缓存的配置与使用
  • 学会缓存的生效条件和失效机制
  • 了解自定义缓存的实现方法
  • 掌握分布式缓存的集成方案
  • 学会缓存性能优化和最佳实践

缓存机制概述

缓存是提高应用性能的重要手段,MyBatis提供了完善的缓存机制。通过缓存,可以减少数据库访问次数,提高查询性能。MyBatis支持一级缓存和二级缓存两种缓存级别。

缓存的作用

  • 减少数据库访问:避免重复查询相同数据
  • 提高响应速度:从内存获取数据比数据库查询快
  • 降低数据库负载:减少数据库连接和查询压力
  • 改善用户体验:更快的响应时间

MyBatis缓存层次

  • 一级缓存:SqlSession级别,默认开启
  • 二级缓存:Mapper级别,需要手动配置
  • 自定义缓存:可以集成第三方缓存框架

一级缓存

一级缓存是SqlSession级别的缓存,在同一个SqlSession中,相同的查询会从缓存中获取结果。

一级缓存的特点

  • 默认开启,无法关闭
  • 作用域为SqlSession
  • 基于HashMap实现
  • 生命周期与SqlSession相同

一级缓存的工作原理

// 演示一级缓存的工作
public class FirstLevelCacheDemo {
    public static void main(String[] args) throws IOException {
        SqlSessionFactory factory = MyBatisUtils.getSqlSessionFactory();
        SqlSession session = factory.openSession();
        
        try {
            UserMapper mapper = session.getMapper(UserMapper.class);
            
            // 第一次查询,从数据库获取
            System.out.println("=== 第一次查询 ===");
            User user1 = mapper.selectUserById(1L);
            System.out.println("用户1: " + user1.getName());
            
            // 第二次查询,从一级缓存获取
            System.out.println("=== 第二次查询 ===");
            User user2 = mapper.selectUserById(1L);
            System.out.println("用户2: " + user2.getName());
            
            // 验证是否为同一个对象
            System.out.println("是否为同一对象: " + (user1 == user2));
            
        } finally {
            session.close();
        }
    }
}

一级缓存失效的情况

  • SqlSession关闭:缓存随之清空
  • 执行增删改操作:会清空当前SqlSession的缓存
  • 手动清除:调用clearCache()方法
  • 查询参数不同:不同参数的查询不会命中缓存
// 演示一级缓存失效
public void testFirstLevelCacheInvalidation() {
    SqlSession session = factory.openSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    
    // 第一次查询
    User user1 = mapper.selectUserById(1L);
    
    // 执行更新操作,缓存失效
    mapper.updateUser(new User(2L, "Updated Name", 25, "[email protected]"));
    
    // 再次查询,需要重新从数据库获取
    User user2 = mapper.selectUserById(1L);
    
    session.close();
}

二级缓存

二级缓存是Mapper级别的缓存,可以在多个SqlSession之间共享。

二级缓存的特点

  • 默认关闭,需要手动开启
  • 作用域为Mapper(Namespace)
  • 可以跨SqlSession共享
  • 需要实体类实现Serializable接口

开启二级缓存

1. 全局配置

<!-- 在mybatis-config.xml中开启二级缓存 -->
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

2. Mapper配置

<!-- 在UserMapper.xml中配置缓存 -->
<mapper namespace="com.example.mybatis.mapper.UserMapper">
    
    <!-- 开启二级缓存 -->
    <cache/>
    
    <!-- 或者使用详细配置 -->
    <cache 
        eviction="LRU"
        flushInterval="60000"
        size="512"
        readOnly="false"/>
    
    <select id="selectUserById" resultType="User">
        SELECT id, name, age, email FROM users WHERE id = #{id}
    </select>
    
</mapper>

3. 实体类配置

// 实体类必须实现Serializable接口
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    
    private Long id;
    private String name;
    private Integer age;
    private String email;
    
    // 构造方法、getter、setter等
}

缓存配置属性

属性 描述 默认值 可选值
eviction 缓存回收策略 LRU LRU、FIFO、SOFT、WEAK
flushInterval 刷新间隔(毫秒) 无 正整数
size 缓存对象数量 1024 正整数
readOnly 是否只读 false true、false

缓存回收策略详解

  • LRU:最近最少使用,移除最长时间不被使用的对象
  • FIFO:先进先出,按对象进入缓存的顺序来移除
  • SOFT:软引用,基于垃圾回收器状态和软引用规则移除对象
  • WEAK:弱引用,更积极地基于垃圾收集器状态和弱引用规则移除对象

二级缓存的使用示例

// 演示二级缓存的工作
public class SecondLevelCacheDemo {
    public static void main(String[] args) throws IOException {
        SqlSessionFactory factory = MyBatisUtils.getSqlSessionFactory();
        
        // 第一个SqlSession
        SqlSession session1 = factory.openSession();
        try {
            UserMapper mapper1 = session1.getMapper(UserMapper.class);
            User user1 = mapper1.selectUserById(1L);
            System.out.println("Session1查询: " + user1.getName());
        } finally {
            session1.close(); // 关闭session1,数据进入二级缓存
        }
        
        // 第二个SqlSession
        SqlSession session2 = factory.openSession();
        try {
            UserMapper mapper2 = session2.getMapper(UserMapper.class);
            User user2 = mapper2.selectUserById(1L); // 从二级缓存获取
            System.out.println("Session2查询: " + user2.getName());
        } finally {
            session2.close();
        }
    }
}

控制缓存行为

1. 禁用特定查询的缓存

<select id="selectUserById" resultType="User" useCache="false">
    SELECT id, name, age, email FROM users WHERE id = #{id}
</select>

2. 控制缓存刷新

<insert id="insertUser" flushCache="true">
    INSERT INTO users (name, age, email) VALUES (#{name}, #{age}, #{email})
</insert>

<update id="updateUser" flushCache="true">
    UPDATE users SET name=#{name}, age=#{age}, email=#{email} WHERE id=#{id}
</update>

<delete id="deleteUser" flushCache="true">
    DELETE FROM users WHERE id = #{id}
</delete>

自定义缓存

MyBatis允许使用自定义缓存实现,可以集成Redis、Ehcache等第三方缓存框架。

实现Cache接口

public class CustomCache implements Cache {
    private final String id;
    private final Map<Object, Object> cache = new ConcurrentHashMap<>();
    
    public CustomCache(String id) {
        this.id = id;
    }
    
    @Override
    public String getId() {
        return id;
    }
    
    @Override
    public void putObject(Object key, Object value) {
        cache.put(key, value);
        System.out.println("缓存存储: " + key);
    }
    
    @Override
    public Object getObject(Object key) {
        Object value = cache.get(key);
        System.out.println("缓存获取: " + key + " -> " + (value != null ? "命中" : "未命中"));
        return value;
    }
    
    @Override
    public Object removeObject(Object key) {
        System.out.println("缓存移除: " + key);
        return cache.remove(key);
    }
    
    @Override
    public void clear() {
        System.out.println("清空缓存");
        cache.clear();
    }
    
    @Override
    public int getSize() {
        return cache.size();
    }
}

使用自定义缓存

<mapper namespace="com.example.mybatis.mapper.UserMapper">
    
    <!-- 使用自定义缓存 -->
    <cache type="com.example.mybatis.cache.CustomCache"/>
    
    <select id="selectUserById" resultType="User">
        SELECT id, name, age, email FROM users WHERE id = #{id}
    </select>
    
</mapper>

Redis缓存集成

在分布式环境中,可以使用Redis作为二级缓存的存储介质。

Redis缓存实现

public class RedisCache implements Cache {
    private final String id;
    private static JedisPool jedisPool;
    
    static {
        // 初始化Redis连接池
        JedisPoolConfig config = new JedisPoolConfig();
        config.setMaxTotal(100);
        config.setMaxIdle(10);
        jedisPool = new JedisPool(config, "localhost", 6379);
    }
    
    public RedisCache(String id) {
        this.id = id;
    }
    
    @Override
    public String getId() {
        return id;
    }
    
    @Override
    public void putObject(Object key, Object value) {
        try (Jedis jedis = jedisPool.getResource()) {
            String keyStr = id + ":" + key.toString();
            String valueStr = serialize(value);
            jedis.setex(keyStr, 3600, valueStr); // 1小时过期
        }
    }
    
    @Override
    public Object getObject(Object key) {
        try (Jedis jedis = jedisPool.getResource()) {
            String keyStr = id + ":" + key.toString();
            String valueStr = jedis.get(keyStr);
            return valueStr != null ? deserialize(valueStr) : null;
        }
    }
    
    @Override
    public Object removeObject(Object key) {
        try (Jedis jedis = jedisPool.getResource()) {
            String keyStr = id + ":" + key.toString();
            return jedis.del(keyStr);
        }
    }
    
    @Override
    public void clear() {
        try (Jedis jedis = jedisPool.getResource()) {
            Set<String> keys = jedis.keys(id + ":*");
            if (!keys.isEmpty()) {
                jedis.del(keys.toArray(new String[0]));
            }
        }
    }
    
    @Override
    public int getSize() {
        try (Jedis jedis = jedisPool.getResource()) {
            return jedis.keys(id + ":*").size();
        }
    }
    
    // 序列化和反序列化方法
    private String serialize(Object obj) {
        // 实现对象序列化
        return JSON.toJSONString(obj);
    }
    
    private Object deserialize(String str) {
        // 实现对象反序列化
        return JSON.parseObject(str, Object.class);
    }
}

配置Redis缓存

<mapper namespace="com.example.mybatis.mapper.UserMapper">
    
    <!-- 使用Redis缓存 -->
    <cache type="com.example.mybatis.cache.RedisCache"
           eviction="LRU"
           flushInterval="600000"
           size="1000"
           readOnly="false"/>
    
</mapper>

缓存最佳实践

1. 缓存策略选择

  • 读多写少的数据适合使用缓存
  • 频繁变更的数据不适合长时间缓存
  • 大对象缓存需要考虑内存占用
  • 关联查询结果缓存需要谨慎处理

2. 缓存配置优化

  • 合理设置缓存大小和过期时间
  • 选择合适的回收策略
  • 根据业务场景选择readOnly属性
  • 监控缓存命中率和性能指标

3. 缓存一致性

  • 及时清除过期缓存
  • 处理好增删改操作对缓存的影响
  • 考虑分布式环境下的缓存同步
  • 避免缓存雪崩和缓存穿透

4. 性能监控

  • 监控缓存命中率
  • 观察缓存大小和内存使用
  • 分析缓存失效原因
  • 定期清理无效缓存

缓存问题与解决方案

Q: 缓存不生效怎么办?

A: 检查全局配置是否开启、Mapper是否配置cache标签、实体类是否实现Serializable接口。

Q: 如何避免缓存数据不一致?

A: 合理设置flushCache属性,在增删改操作后及时清除相关缓存。

Q: 二级缓存序列化异常?

A: 确保所有相关实体类都实现了Serializable接口,包括关联对象。

Q: 缓存占用内存过大?

A: 调整缓存大小配置,选择合适的回收策略,避免缓存大对象。

Q: 分布式环境缓存同步问题?

A: 使用Redis等分布式缓存,或者实现缓存失效通知机制。

实践练习

练习1:一级缓存验证

编写代码验证一级缓存的生效和失效条件。

练习2:二级缓存配置

配置二级缓存,测试不同SqlSession间的缓存共享。

练习3:自定义缓存实现

实现一个带有统计功能的自定义缓存。

练习4:Redis缓存集成

集成Redis作为二级缓存,测试分布式缓存效果。

练习5:缓存性能测试

对比有缓存和无缓存的性能差异,分析缓存命中率。

运行示例程序

步骤1:导入项目

将chapter-09-cache.json导入到IntelliJ IDEA中。

步骤2:配置环境

确保MySQL和Redis服务正常运行,执行init.sql创建测试数据。

步骤3:运行基础示例

运行CacheDemo.java,观察一级缓存和二级缓存的工作效果。

步骤4:观察缓存日志

查看控制台输出,理解缓存的命中和失效机制。

步骤5:测试自定义缓存

运行自定义缓存示例,观察缓存的存储和获取过程。

步骤6:Redis缓存测试

配置Redis缓存,测试分布式缓存的效果。

步骤7:运行测试用例

执行CacheTest.java中的测试用例,验证各种缓存场景。
本章小结

📖 本章小结

本章我们学习了 缓存机制 的相关内容,包括:

  • 核心概念和基本原理
  • 配置方法和实践技巧
  • 应用场景和最佳实践
  • 常见问题和解决方案

下一章节 🎉

恭喜完成本章学习!

你已经掌握了 缓存机制 的核心知识
第10章插件开发继续学习 MyBatis 的更多特性和应用⏱️预计 30 分钟📊中等难度开始学习 → ← 第08章 📚 课程目录 第10章 →

第09章 缓存机制
https://clxhxhhr.top/posts/3062/
作者
clxstart
发布于
2026-09-24
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。