MyBatis缓存机制

前言

在日常开发中,MyBatis 是我们接触最多的持久层框架之一,它以简化 JDBC 操作、灵活的 SQL 编写能力,成为了很多Java 开发者的首选

而缓存,作为MyBatis性能优化的核心手段,更是学习的核心重点

一、MyBatis执行流程

先来了解一下MyBatis的执行流程,再来看缓存到底怎么优化性能的

执行流程可以分为两大阶段:初始化阶段、执行阶段,而缓存的核心载体sqlsession就是在执行阶段诞生的

1.初始化阶段:构建全局配置与工厂

项目启动时,MyBatis 会一次性完成配置加载与核心对象构建:

  • 读取 mybatis-config.xml 核心配置文件,以及所有 Mapper XML / 注解接口;
  • 将所有配置(数据源、事务、映射关系、SQL 语句)解析并封装为全局唯一的 Configuration 对象;
  • 通过 SqlSessionFactoryBuilder 构建 SqlSessionFactory,这是一个全局单例的工厂对象,负责创建 SqlSession

2.执行阶段:一次请求的完整链路

每次业务请求到来,MyBatis 都会走一遍以下流程,而缓存的作用就藏在其中:

  • 通过 SqlSessionFactory.openSession() 创建 SqlSession 对象,这是与数据库交互的核心会话;
  • SqlSession 中获取 Mapper 接口的动态代理对象;
  • 调用 Mapper 方法时,代理对象会找到对应的 SQL 语句,交由 Executor 执行器处理;
  • Executor 会先尝试从缓存中获取数据,未命中则执行 JDBC 查询,并将结果存入缓存;
  • 将查询结果映射为 Java 对象,返回给业务层,最终关闭 SqlSession

从流程中可以看出:缓存是 SqlSessionExecutor 为了减少数据库访问而内置的优化机制 ,而 MyBatis 的缓存体系,也正是围绕 SqlSession 展开的。

整体大致流程如下:

二、SqlSession:MyBatis缓存的核心载体

从上面图里可以看出sqlsession相当于mybatis和数据库的桥梁,不仅如此还是缓存的相关载体

1.SqlSession的核心定位

sqlsession可以理解为:一次数据库会话,一次请求的专属操作对象

  • 它封装了数据库连接、事务控制(提交 / 回滚);
  • 提供了增删改查的 API,也能获取 Mapper 代理对象;
  • 持有默认开启的一级缓存(本地缓存),这是 MyBatis 最基础的缓存实现。

2. SqlSession 的生命周期

为了好理解缓存失效,我们先来搞懂sqlsession的生命周期:

  • SqlSessionFactory:全局单例,与应用同生命周期,不持有任何缓存;
  • SqlSession :线程不安全,生命周期为一次请求 / 一次方法调用 ,每次请求都会创建新的 SqlSession,请求结束后关闭销毁;
  • 一级缓存是 SqlSession 级别的,因此它的生命周期和 SqlSession 完全绑定,会话关闭,缓存也随之销毁。

理解了这一点,再去看 MyBatis 的缓存体系,就会豁然开朗。

三、缓存

1.什么是缓存

缓存是一种临时数据存储机制,它将频繁访问、查询成本高的数据,存储在读取速度更快的介质(如内存)中,当再次请求相同数据时,直接从缓存中读取,避免重复访问底层数据源(如数据库),从而提升系统响应速度,降低数据库压力。

简单来说:缓存就是 "把常用数据存起来,下次直接用,不用再查数据库"。

2.为什么使用缓存

缓存的核心价值,在于解决数据库性能瓶颈:

  • 降低数据库压力:重复查询直接命中缓存,减少数据库访问次数;

  • 提升系统响应速度:内存读取速度远快于数据库磁盘 IO,用户体验更好;

  • 减少网络开销:避免重复的数据库连接与数据传输,降低网络负载。

3. 什么样的数据适合使用缓存

不是所有数据都适合缓存,使用缓存前必须判断数据的特性:

  • 读多写少:比如字典表、配置表、用户信息等,查询频率远高于修改频率;

  • 数据一致性要求不高:短时间内数据不一致不影响业务(如商品库存,可接受几秒延迟);

  • 数据不频繁变动:频繁更新的数据会导致缓存频繁失效,反而增加开销;

  • 非核心业务数据:核心交易数据(如订单金额)不建议使用缓存,避免数据不一致导致业务错误。

四、MyBatis缓存体系:一级缓存+二级缓存

MyBatis 内置了两级缓存,默认开启一级缓存,二级缓存需要手动配置。我们先从最基础的一级缓存讲起。

1. 一级缓存(本地缓存)

一级缓存是 MyBatis 最基础的缓存,也是默认开启、无法关闭的缓存机制。

(1)什么是一级缓存

一级缓存也叫本地缓存 ,是 SqlSession 级别的缓存,它的核心特性如下:

  • 作用域 :同一个 SqlSession 会话内;

  • 存储介质 :内存(底层是 HashMap);

  • 默认状态:强制开启,无法关闭;

  • 缓存 Key :由 Statement ID + SQL 语句 + 参数 + 分页条件 组成,确保只有完全相同的查询才能命中缓存。

实际上就是存储一个对象,一旦条件改变,那么这个缓存就会失效

(2)测试例子

可以通过一段代码来理解"本质是存储一个对象"这句话:第二次查询直接从一级缓存中获取了数据,而不是访问数据库

java 复制代码
public class FirstLevelCacheTest {
    public static void main(String[] args) {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        // 第一次查询:访问数据库,结果存入一级缓存
        User user1 = mapper.selectById(1);
        System.out.println(user1);

        // 第二次查询:同一SqlSession、相同SQL,直接命中缓存,不访问数据库
        User user2 = mapper.selectById(1);
        System.out.println(user2);

        // 验证:两个对象是同一个(内存地址相同)
        System.out.println(user1 == user2); // 输出 true
        sqlSession.close();
    }
}

(3)缓存失效

刚刚提到了只要改变条件,这个缓存就会失效,那缓存失效还有哪些场景?

① SqlSession执行写操作并提交事务:当同一个 SqlSession 执行 insert/update/delete 操作并调用 commit() 时,会清空该会话的一级缓存,避免脏数据。

② 手动调用clearCache ( ) :调用 sqlSession.clearCache() 会直接清空当前会话的一级缓存。

③ 关闭SqlSession会话:SqlSession关闭后,缓存对象被销毁,缓存自然失效

④ 不同的SqlSession会话:一级缓存是会话级别的,不同 SqlSession 的缓存相互隔离,无法共享。

⑤ 查询条件发生变化:两次查询的 SQL、参数、分页条件不同,缓存 Key 不匹配,无法命中缓存。

2.二级缓存(全局缓存)

一级缓存只能在同一个 SqlSession 中生效,无法跨会话共享。为了解决这个问题,MyBatis 提供了二级缓存 ,它是 Mapper 命名空间级别的缓存,跨 SqlSession 共享。

如果说一级缓存存的是对象,那么二级缓存村的就是数据(可以存很多条)

二级缓存开启的条件

二级缓存默认关闭,需要同时满足以下条件才能生效:

① 全局配置中开启二级缓存:在 mybatis-config.xml 中设置 cacheEnabled=true(默认就是 true,可省略):

java 复制代码
<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

② 在对应的Mapper xml中配置<cache/>标签,该标签意味着当前命名空间下所有查询都是用二级缓存:

java 复制代码
<!-- UserMapper.xml 中开启二级缓存 -->
<mapper namespace="com.example.mapper.UserMapper">
    <cache/>
    <select id="selectById" resultType="com.example.pojo.User">
        select * from user where id = #{id}
    </select>
</mapper>

③ 序列化接口:二级缓存会将数据序列化后存储,因此实体类要实现序列化接口(implement Serializable)

④ SqlSession必须提交或关闭:二级缓存的数据不会在查询后立即写入,只有当 SqlSession 调用 commit()close() 时,才会将数据写入二级缓存。

3.缓存查询顺序

MyBatis 执行查询操作时,会按照以下顺序查找数据:

(1)先查二级缓存:根据 Mapper 命名空间 + 缓存 Key,查找全局缓存;

命中:直接返回数据;

未命中:进入下一步。

(2)再查一级缓存 :在当前 SqlSession 会话内,根据缓存 Key 查找本地缓存;

命中:直接返回数据;

未命中:进入下一步。

(3)最后查询数据库:执行 JDBC 查询,获取数据后:

存入当前会话的一级缓存;

会话提交 / 关闭后,写入二级缓存。

相关推荐
huipeng9262 小时前
企业级微服务开发实战(一):项目启动与工程化设计
java·开发语言·spring boot·spring cloud·微服务·云原生·架构
爱吃土豆的马铃薯ㅤㅤㅤㅤㅤㅤㅤㅤㅤ2 小时前
java实现excel导入、下载模板方法
java·开发语言·excel
CAE虚拟与现实3 小时前
Redis如何保证存和读的过程中数据的一致性?
数据库·redis·缓存
段ヤシ.3 小时前
回顾Java知识点,面试题汇总Day12(持续更新)
java·mybatis
java1234_小锋3 小时前
Spring AI 2.0 开发Java Agent智能体 - MCP(模型上下文协议)
java·人工智能·spring·spring ai
seven97_top3 小时前
两小时入门Sentinel
java·sentinel
叶小鸡3 小时前
Java 篇-项目实战-AI 天机学堂(从 0 到 1)-day1
java·开发语言
bigbearxyz3 小时前
Caused by: java.net.SocketException: Connection reset问题排查
java·keepalived·proxysql
500845 小时前
昇腾 CANN 的五层架构,到底分了哪五层
java·人工智能·分布式·架构·ocr·wpf