MyBatis实战指南(七)MyBatis缓存机制

MyBatis实战指南(七)MyBatis缓存机制

  • 前言
  • [一、 为什么需要缓存?](#一、 为什么需要缓存?)
  • [二、 MyBatis的缓存层次](#二、 MyBatis的缓存层次)
  • [三、 一级缓存](#三、 一级缓存)
    • [3.1 一级缓存(本地缓存)](#3.1 一级缓存(本地缓存))
    • [3.2 一级缓存的工作原理](#3.2 一级缓存的工作原理)
    • [3.3 一级缓存示例](#3.3 一级缓存示例)
    • [3.4 一级缓存的特点](#3.4 一级缓存的特点)
    • [3.5 一级缓存的优缺点](#3.5 一级缓存的优缺点)
  • [四、 二级缓存](#四、 二级缓存)
    • [4.1 什么是二级缓存?](#4.1 什么是二级缓存?)
    • [4.2 为什么需要二级缓存?](#4.2 为什么需要二级缓存?)
    • [4.3 如何配置二级缓存?](#4.3 如何配置二级缓存?)
    • [4.4 自定义二级缓存配置](#4.4 自定义二级缓存配置)
    • [4.5 可用的清除策略](#4.5 可用的清除策略)
    • [4.6 二级缓存的特点](#4.6 二级缓存的特点)
    • [4.7 二级缓存的使用示例](#4.7 二级缓存的使用示例)
  • [五、 一级缓存和二级缓存的对比表格](#五、 一级缓存和二级缓存的对比表格)

前言

  • 在上一篇博客中,我们深入解析了 MyBatis XML 实战的核心要点,围绕自动映射机制展开全面讲解,从字段命名匹配规则到驼峰转换配置(mapUnderscoreToCamelCase),再到自动映射行为控制(autoMappingBehavior),通过具体案例演示了如何利用 MyBatis 的自动映射能力简化对象与数据库的映射流程
  • 本篇博客将聚焦 MyBatis 的缓存优化体系,从底层原理到实战配置进行分层拆解

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343

我的MyBatis实战指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12969707.html?spm=1001.2014.3001.5482


一、 为什么需要缓存?

  • 想象一下,你有一个网站,每次用户访问页面都要从数据库查询相同的数据
  • 如果这个页面每天有1000人访问,那就意味着数据库要执行1000次相同的查询。
  • 这会给数据库带来很大的压力,甚至可能导致数据库崩溃

缓存就是解决这个问题的好办法。

它就像一个临时存储区,把经常访问的数据存起来,下次再需要这些数据时,直接从缓存中拿,而不需要再去访问数据库,这样可以大大提高系统性能。

二、 MyBatis的缓存层次

MyBatis有两级缓存机制:

  1. 一级缓存(本地缓存):默认开启,只在当前会话(SqlSession)中有效
  2. 二级缓存(全局缓存):需要手动配置,可以在多个会话之间共享

三、 一级缓存

3.1 一级缓存(本地缓存)

一级缓存是MyBatis的默认缓存机制,它是会话级别的缓存。这意味着:

  • 同一个SqlSession中执行相同的查询,只会执行一次数据库查询,后续查询会直接从缓存中获取结果
  • 当SqlSession关闭后,该会话的一级缓存也会被清空
  • 当执行insert、update、delete操作时,会清空当前会话的一级缓存

3.2 一级缓存的工作原理

  1. 当你执行一个查询时,MyBatis首先会在当前会话的缓存中查找是否有相同的查询结果
  2. 如果有,直接从缓存中返回结果,不再执行数据库查询
  3. 如果没有,执行数据库查询,并将结果存入缓存

3.3 一级缓存示例

java 复制代码
// 创建第一个会话
SqlSession session1 = sqlSessionFactory.openSession();
try {
    // 第一次查询,会执行SQL
    User user1 = session1.selectOne("selectUserById", 1);
    System.out.println(user1.getName());
    
    // 第二次查询相同ID,不会执行SQL,直接从缓存获取
    User user2 = session1.selectOne("selectUserById", 1);
    System.out.println(user2.getName());
    
    // 两次查询获取的是同一个对象引用
    System.out.println(user1 == user2); // 输出 true
} finally {
    session1.close();
}

// 创建第二个会话
SqlSession session2 = sqlSessionFactory.openSession();
try {
    // 在新会话中查询相同ID,会执行SQL
    User user3 = session2.selectOne("selectUserById", 1);
    System.out.println(user3.getName());
} finally {
    session2.close();
}

3.4 一级缓存的特点

  • 自动启用:不需要任何配置,默认就会使用
  • 会话隔离:不同会话之间的缓存互不影响
  • 轻量级:实现简单,开销小
  • 生命周期短:随会话关闭而清空

3.5 一级缓存的优缺点

优点

  • 实现简单,不需要额外配置
  • 减少了同一会话内的重复查询,提高了性能
  • 对应用透明,不需要修改业务代码

缺点

  • 作用域太小,仅限于当前会话
  • 不能在应用重启后保留
  • 无法在分布式环境中共享

四、 二级缓存

4.1 什么是二级缓存?

二级缓存是MyBatis的全局缓存,它可以在多个SqlSession之间共享数据。这意味着:

  • 不同的会话查询相同的数据时,可以从同一个缓存中获取
  • 应用程序重启前,缓存数据会一直存在(除非被手动刷新)
  • 适合缓存那些不经常变化、经常被查询的数据

4.2 为什么需要二级缓存?

一级缓存虽然能提高同一会话内的查询性能,但在以下场景下显得不足:

  • 跨会话查询相同数据时,仍然会多次访问数据库
  • 在分布式系统中,不同服务实例无法共享缓存
  • 对于高并发系统,一级缓存的作用有限

二级缓存正是为了解决这些问题而设计的。

4.3 如何配置二级缓存?

要启用二级缓存,只需在SQL映射文件中添加<cache/>标签:

xml 复制代码
<mapper namespace="com.example.mapper.UserMapper">
    <!-- 启用二级缓存 -->
    <cache/>
    
    <!-- 查询语句 -->
    <select id="selectUserById" resultType="User">
        SELECT * FROM users WHERE id = #{id}
    </select>
</mapper>

这行简单的配置会产生以下效果:

  • 该映射文件中所有SELECT语句的结果会被缓存
  • 所有INSERT、UPDATE、DELETE语句会刷新缓存
  • 默认使用LRU(最近最少使用)策略清除缓存
  • 最多存储1024个对象引用
  • 缓存对象可读写(返回的是对象副本)

4.4 自定义二级缓存配置

你可以通过<cache/>标签的属性来自定义缓存行为:

xml 复制代码
<cache
    eviction="FIFO"                <!-- 清除策略:先进先出 -->
    flushInterval="60000"          <!-- 刷新间隔:60秒 -->
    size="512"                     <!-- 缓存大小:最多512个对象 -->
    readOnly="true"/>              <!-- 只读缓存:提高性能但返回共享对象 -->

4.5 可用的清除策略

MyBatis提供了四种缓存清除策略:

  1. LRU(默认):移除最长时间不被使用的对象
  2. FIFO:按对象进入缓存的顺序移除
  3. SOFT:基于JVM垃圾回收器状态和软引用规则移除
  4. WEAK:更激进的回收策略,基于弱引用规则移除

4.6 二级缓存的特点

  1. 全局共享:多个SqlSession可以共享同一个缓存
  2. 需要手动配置:默认不启用,必须在映射文件中声明
  3. 会话间隔离:每个命名空间(通常对应一个Mapper接口)有独立的缓存
  4. 事务支持:在事务提交或回滚时,缓存会被正确刷新
  5. 序列化要求 :缓存对象必须实现Serializable接口

4.7 二级缓存的使用示例

java 复制代码
// 第一个会话
SqlSession session1 = sqlSessionFactory.openSession();
try {
    UserMapper mapper1 = session1.getMapper(UserMapper.class);
    User user1 = mapper1.selectUserById(1);  // 执行SQL查询
    session1.commit();  // 提交事务,将结果存入二级缓存
} finally {
    session1.close();
}

// 第二个会话
SqlSession session2 = sqlSessionFactory.openSession();
try {
    UserMapper mapper2 = session2.getMapper(UserMapper.class);
    User user2 = mapper2.selectUserById(1);  // 直接从二级缓存获取
    System.out.println(user2.getName());
} finally {
    session2.close();
}

五、 一级缓存和二级缓存的对比表格

对比维度 一级缓存(本地缓存) 二级缓存(全局缓存)
作用域 仅在当前SqlSession会话内有效 SqlSession会话共享,属于全局范围
默认状态 自动启用(无需配置) 需手动在Mapper文件中添加<cache/>标签启用
缓存生命周期 SqlSession关闭而清空 随应用重启或手动刷新才会清空
数据共享 仅在当前会话内共享,不同会话间不共享 同一Mapper命名空间下的所有会话共享
刷新机制 执行insert/update/delete操作时自动清空当前会话缓存 执行insert/update/delete操作时清空全局缓存
缓存策略配置 无需配置,使用固定策略 可自定义配置(清除策略、刷新间隔、缓存大小等)
缓存对象要求 无特殊要求(直接引用对象) 对象需实现Serializable接口(支持序列化)
性能影响 开销小(轻量级,无序列化开销) 存在序列化/反序列化开销,可通过readOnly优化
适用场景 同一会话内的重复查询、短生命周期事务 跨会话的重复查询、不常更新且频繁访问的数据
分布式支持 不支持(仅单JVM内有效) 原生不支持,需集成第三方缓存(如Redis、Memcached)
典型场景示例 单事务内多次查询同一数据 系统字典表、配置表的全局缓存

以上就是这篇博客的全部内容,下一篇我们将继续探索MyBatis的更多精彩内容。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343

我的MyBatis实战指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_12969707.html?spm=1001.2014.3001.5482

|--------------------|
| 非常感谢您的阅读,喜欢的话记得三连哦 |

相关推荐
hweiyu001 分钟前
MySQL视图介绍
数据库·mysql
程序员阿斌哥19 分钟前
记录一次jenkins slave因为本地安装多个java版本导致的问题
java·jenkins
海天瑞声AI20 分钟前
六月上新!多语种正则/逆正则数据集、5000小时中文双工数据集、经典人物IP语音合成数据集……
数据库·人工智能·自然语言处理·语音识别
Zfox_21 分钟前
Redis:渐进式遍历
服务器·数据库·redis·缓存
WHFENGHE21 分钟前
输电线防山火在线监测装置:科技赋能电网安全防线
科技·物联网·安全
铃木隼.24 分钟前
Postgresql日常维护
数据库·postgresql
爱编程的小新☆27 分钟前
【MySQL】视图
数据库·mysql
银空飞羽29 分钟前
两个一样的MCP,大模型会怎么选择
安全·mcp·trae
张哈大31 分钟前
【 java 虚拟机知识 第二篇 】
java·开发语言·jvm·笔记
GreatSQL32 分钟前
工具分享-从ibd文件中恢复数据的神器ibd2sql
数据库