【Java 开发日记】我们来说一下 Mybatis 的缓存机制

【Java 开发日记】我们来说一下 Mybatis 的缓存机制

目录

核心概览

一级缓存

1. 作用域

2. 工作机制

3. 示例说明

4. 注意事项

二级缓存

1. 作用域

2. 开启与配置

3. 工作机制

4. 示例说明

5. 注意事项

缓存顺序与总结

使用建议


核心概览

  • 一级缓存:默认开启,作用范围在 同一个 SqlSession 内。
  • 二级缓存:需要手动配置开启,作用范围在 同一个 Mapper 命名空间(即同一个 Mapper 接口)内,可以被多个 SqlSession 共享。

一级缓存

1. 作用域
  • SqlSession 级别:当同一个 SqlSession 执行相同的 SQL 查询时,MyBatis 会优先从缓存中获取数据,而不是直接查询数据库。
  • 它是 默认开启 的,无法关闭,但可以配置其作用范围(SESSIONSTATEMENT)。
2. 工作机制
  1. 第一次执行查询后,查询结果会被存储到 SqlSession 关联的一级缓存中。
  2. 在同一个 SqlSession 中,再次执行 完全相同的 SQL 查询(包括语句和参数)时,会直接返回缓存中的对象,而不会去数据库查询。
  3. 如果 SqlSession 执行了 增(INSERT)、删(DELETE)、改(UPDATE) 操作,或者调用了 commit()close()rollback() 方法,该 SqlSession 的一级缓存会被清空。这是为了防止读取到脏数据。
3. 示例说明
// 假设获取的 SqlSession 和 UserMapper try (SqlSession sqlSession = sqlSessionFactory.openSession()) { UserMapper mapper = sqlSession.getMapper(UserMapper.class); // 第一次查询,会发送 SQL 到数据库 User user1 = mapper.selectUserById(1L); System.out.println(user1); // 第二次查询,SQL 和参数完全相同,直接从一级缓存返回,不查询数据库 User user2 = mapper.selectUserById(1L); System.out.println(user2); // 判断是否为同一个对象(是,因为从缓存中返回的是同一个对象的引用) System.out.println(user1 == user2); // 输出:true // 执行一个更新操作 mapper.updateUser(user1); // 此时,一级缓存被清空 // 第三次查询,因为缓存被清空,会再次发送 SQL 到数据库 User user3 = mapper.selectUserById(1L); System.out.println(user3 == user1); // 输出:false (虽然是同一条数据,但已是新对象) }
4. 注意事项
  • 对象相同:一级缓存返回的是 同一个对象的引用,因此在同一个 SqlSession 内,你操作的都是同一个 Java 对象。
  • 分布式环境:一级缓存无法在多个应用服务器之间共享,因为它绑定在单个请求的 SqlSession 上。

二级缓存

1. 作用域
  • Mapper 级别 / Namespace 级别:多个 SqlSession 在访问同一个 Mapper 的查询时,可以共享其缓存。
  • 它是 默认关闭 的,需要在全局配置中开启,并在具体的 Mapper XML 中显式配置。
2. 开启与配置

a. 全局配置文件 (mybatis-config.xml)
必须显式设置开启二级缓存(虽然默认是 true,但显式声明是个好习惯)。

<configuration> <settings> <!-- 开启全局二级缓存,默认就是 true,但建议写明 --> <setting name="cacheEnabled" value="true"/> </settings> </configuration>

b. 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"> <!-- 开启本 Mapper 的二级缓存 --> <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> <!-- 其他 SQL 定义 --> <select parameterType="long" resultType="User" useCache="true"> SELECT * FROM user WHERE id = #{id} </select> </mapper>
  • <cache/> 标签属性:
    • eviction:缓存回收策略。
      • LRU(默认):最近最少使用。
      • FIFO:先进先出。
      • SOFT:软引用,基于垃圾回收器状态和软引用规则移除。
      • WEAK:弱引用,更积极地移除。
    • flushInterval:缓存刷新间隔(毫秒),默认不清空。
    • size:缓存存放多少元素。
    • readOnly:是否为只读。
      • true:返回相同的缓存对象实例,性能好,但不允许修改。
      • false(默认):通过序列化返回缓存对象的拷贝,安全,性能稍差。
3. 工作机制
  1. 当一个 SqlSession 执行查询后,在关闭或提交时,其查询结果会被存入二级缓存。
  2. 另一个 SqlSession 执行相同的查询时,会先从二级缓存中查找数据。如果找到,则直接返回,否则再去数据库查询。
  3. 任何一个 SqlSession 执行了 增、删、改 操作并 commit() 后,会清空 整个对应 Mapper 的二级缓存,以保证数据一致性。
4. 示例说明
// 第一个 SqlSession try (SqlSession sqlSession1 = sqlSessionFactory.openSession()) { UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class); User user1 = mapper1.selectUserById(1L); // 查询数据库 sqlSession1.close(); // 关闭时,数据存入二级缓存 } // 第二个 SqlSession(与第一个不同) try (SqlSession sqlSession2 = sqlSessionFactory.openSession()) { UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class); // 查询相同的 SQL,直接从二级缓存获取,不查询数据库 User user2 = mapper2.selectUserById(1L); } // 第三个 SqlSession,执行了更新 try (SqlSession sqlSession3 = sqlSessionFactory.openSession()) { UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class); User user = mapper3.selectUserById(1L); user.setName("New Name"); mapper3.updateUser(user); // 执行更新 sqlSession3.commit(); // 提交时,清空 UserMapper 的二级缓存 } // 第四个 SqlSession try (SqlSession sqlSession4 = sqlSessionFactory.openSession()) { UserMapper mapper4 = sqlSession4.getMapper(UserMapper.class); // 因为缓存已被清空,所以会再次查询数据库 User user4 = mapper4.selectUserById(1L); }
5. 注意事项
  • 实体类序列化:如果二级缓存的 readOnly="false",那么对应的实体类必须实现 Serializable 接口。
  • 事务提交:只有在 SqlSession 执行 commit()close() 时,数据才会从一级缓存转存到二级缓存。
  • 缓存粒度:二级缓存是 Mapper 级别的,有时会显得比较粗粒度。可以通过 <cache-ref> 让多个 Mapper 共享一个缓存,但不推荐,容易引起数据混乱。

缓存顺序与总结

当发起一个查询请求时,MyBatis 的缓存查询顺序是:

  1. 先查二级缓存:查看当前 Mapper 的二级缓存中是否有数据。
  2. 再查一级缓存:如果二级缓存没有,再查看当前 SqlSession 的一级缓存中是否有数据。
  3. 最后查数据库:如果两级缓存都没有,才发送 SQL 语句到数据库执行查询。

查询到的数据会 先存入一级缓存,在 SqlSession 关闭或提交时,再转存到二级缓存

特性

一级缓存

二级缓存

作用域

SqlSession

Mapper (Namespace)

默认状态

开启

关闭

是否共享

否,Session 独享

是,跨 Session 共享

清空时机

UPDATE/INSERT/DELETE, commit(), close()

同 Mapper 的 UPDATE/INSERT/DELETE + commit()

使用建议

  • 查询多,修改少的数据适合使用二级缓存,如字典表、配置项。
  • 数据实时性要求高的场景(如交易、订单)应谨慎使用二级缓存,或者设置较短的刷新间隔。
  • 在分布式环境中,默认的二级缓存(基于内存)是无法共享的,需要集成 Redis、Ehcache 等第三方缓存中间件来替代。
  • 理解缓存机制有助于解决一些“诡异”的问题,比如在同一个事务中,先后查询和更新,但由于一级缓存的存在,后续查询可能看不到其他线程的更新。

如果小假的内容对你有帮助,请点赞评论收藏。创作不易,大家的支持就是我坚持下去的动力!

Read more

2026年最新版Python安装和PyCharm安装教程(图文详细 附安装包)

2026年最新版Python安装和PyCharm安装教程(图文详细 附安装包)

2026年最新版Python安装和PyCharm安装教程 * 前言:安装前友好提示 * 一、Python安装 * 1、 下载python安装包 * 2、 安装python * 3、验证安装成功 * 二、 安装Pycharm * 1、Pycharm介绍 * 2、Pycharm安装 * 3、Pycharm使用 前言:安装前友好提示 (1) 避免安装路径有中文 / 空格; (2) 如果非指定版本建议安装最新版; (3) 无特殊要求随便选一个python版本(建议3.11.9) (4)pycharm无特殊要求建议选择2025版本 一、Python安装 1、 下载python安装包 Python-Pycharm安装包:https://pan.quark.cn/s/6878d7cc5460 安装包我已经下载好了,点击上面网盘链接直接获取就行 2、 安装python 双击下载好的 .exe

By Ne0inhk

用 Python 批量下载全量 A 股历史行情数据:基于 AKShare 的高效实践

关键词:AKShare, A股数据, 股票历史行情, 量化分析, Python 金融, 断点续传 适用读者:量化交易初学者、金融数据分析师、Python 爱好者、学术研究者 💡 为什么需要本地化 A 股历史数据? 在量化投资、策略回测、因子挖掘等场景中,高质量、完整、本地存储的历史行情数据是不可或缺的基础。然而: * 商业数据接口(如 Wind、Tushare Pro)往往收费或有调用限制; * 免费接口(如早期 Tushare)可能不稳定或字段不全; * 网页爬虫易被反爬,维护成本高。 幸运的是,开源项目 AKShare 提供了免费、稳定、覆盖全面的中国金融市场数据接口,包括: * A 股日线、分钟线 * 指数、基金、期货、期权

By Ne0inhk
新手向:C语言、Java、Python 的选择与未来指南

新手向:C语言、Java、Python 的选择与未来指南

语言即工具,选对方向比埋头苦学更重要 你好,编程世界的新朋友!当你第一次踏入代码的宇宙,面对形形色色的编程语言,是否感到眼花缭乱?今天我们就来聊聊最主流的三种编程语言——C语言、Java 和 Python——它们各自是谁,适合做什么,以及未来十年谁能带你走得更远。 一、编程世界的三把钥匙:角色定位 如果把编程比作建造房屋,那么: * C语言是钢筋骨架:诞生于1972年,它直接与计算机硬件“对话”,负责构建最基础的支撑结构。 * Java是精装套房:1995年问世,以“一次编写,到处运行”闻名,擅长打造稳定、可复用的功能模块。 * Python是智能管家:1991年出生却在近十年大放异彩,像一位高效助手,用最少的指令完成复杂任务13。 二、核心差异对比:从底层到应用 1. 语言类型与设计哲学 * C语言:属于面向过程的编译型语言。代码在执行前需全部翻译成机器指令,运行效率极高,但需要开发者手动管理内存(类似自己打扫房间)15。 * Java:

By Ne0inhk