当前位置: 首页 > news >正文

MyBatis之缓存机制详解

MyBatis之缓存机制详解

    • 一、MyBatis缓存的基本概念
      • 1.1 缓存的核心价值
      • 1.2 MyBatis的两级缓存体系
    • 二、一级缓存(SqlSession级别缓存)
      • 2.1 工作原理
      • 2.2 实战案例:一级缓存演示
        • 2.2.1 基础用法(默认开启)
        • 2.2.2 一级缓存失效场景
      • 2.3 一级缓存的特点与适用场景
    • 三、二级缓存(Mapper级别缓存)
      • 3.1 工作原理
      • 3.2 二级缓存的开启与配置
        • 3.2.1 全局配置(可选)
        • 3.2.2 Mapper接口开启缓存
        • 3.2.3 实体类序列化(必须)
      • 3.3 实战案例:二级缓存演示
      • 3.4 二级缓存的特点与适用场景
    • 四、二级缓存的高级配置
      • 4.1 禁用特定查询的二级缓存
      • 4.2 强制刷新二级缓存
      • 4.3 整合第三方缓存(如Redis)
        • 4.3.1 引入依赖(Redis+MyBatis-Redis)
        • 4.3.2 配置Redis缓存
    • 五、缓存使用的常见问题与避坑指南
      • 5.1 一级缓存导致的脏读问题
      • 5.2 二级缓存的序列化问题
      • 5.3 缓存与事务的一致性问题
      • 5.4 过度使用缓存导致内存溢出

缓存是提升数据库查询性能的关键技术,MyBatis内置了两级缓存机制,能有效减少重复查询的数据库交互,降低数据库压力。

一、MyBatis缓存的基本概念

1.1 缓存的核心价值

数据库查询是应用性能的常见瓶颈(磁盘IO比内存IO慢10^6倍以上),缓存通过将频繁查询的结果存储在内存中,避免重复访问数据库,从而:

  • 减少数据库连接和SQL执行次数;
  • 降低数据库服务器压力;
  • 提升应用响应速度(从内存读取比数据库查询快100倍以上)。

1.2 MyBatis的两级缓存体系

MyBatis提供两级缓存,工作流程如下:

  1. 一级缓存(SqlSession级别):默认开启,缓存当前会话(SqlSession)的查询结果;
  2. 二级缓存(Mapper级别):需手动开启,缓存Mapper接口的查询结果,可被多个SqlSession共享。

查询数据时,MyBatis的缓存查询顺序:

二级缓存 → 一级缓存 → 数据库

即先查二级缓存,若未命中则查一级缓存,仍未命中才查询数据库。

二、一级缓存(SqlSession级别缓存)

一级缓存是MyBatis的默认缓存,绑定到SqlSession(会话),生命周期与SqlSession一致。

2.1 工作原理

  • 缓存范围:每个SqlSession拥有独立的一级缓存,不同SqlSession的缓存互不影响;
  • 缓存时机SqlSession执行select查询后,会将结果存入一级缓存;
  • 命中条件:相同的Mapper方法+相同的参数+相同的SQL
  • 失效场景SqlSession执行insert/update/delete(会清空当前SqlSession的一级缓存)、SqlSession关闭或提交。

2.2 实战案例:一级缓存演示

2.2.1 基础用法(默认开启)
// 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// 第一次查询(未命中缓存,查询数据库)
User user1 = userMapper.selectById(1); // 第二次查询(相同SqlSession+相同参数,命中一级缓存,不查数据库)
User user2 = userMapper.selectById(1); System.out.println(user1 == user2); // true(同一对象,从缓存获取)sqlSession.close(); // 关闭会话,一级缓存失效
2.2.2 一级缓存失效场景
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);User user1 = userMapper.selectById(1); // 执行更新操作(insert/update/delete),清空一级缓存
userMapper.updateAge(1, 25); 
sqlSession.commit(); // 提交事务(触发缓存清空)// 再次查询(缓存已清空,重新查询数据库)
User user2 = userMapper.selectById(1); 
System.out.println(user1 == user2); // false(不同对象,从数据库获取)

2.3 一级缓存的特点与适用场景

特点说明
默认开启无需配置,开箱即用
会话隔离不同SqlSession的缓存独立,避免数据冲突
自动管理增删改自动清空缓存,保证数据一致性

适用场景

  • 单会话内的频繁查询(如同一请求中多次查询相同用户信息);
  • 读多写少的场景(避免频繁查询数据库)。

三、二级缓存(Mapper级别缓存)

二级缓存是跨SqlSession的全局缓存,绑定到Mapper接口(同一Mapper的所有方法共享),需手动开启。

3.1 工作原理

  • 缓存范围:同一Mapper接口的所有SqlSession共享二级缓存;
  • 缓存时机SqlSession关闭(close())或提交(commit())后,一级缓存的结果会写入二级缓存;
  • 命中条件:相同的Mapper接口+相同的方法+相同的参数
  • 失效场景:Mapper接口执行insert/update/delete(会清空当前Mapper的二级缓存)。

3.2 二级缓存的开启与配置

3.2.1 全局配置(可选)

mybatis-config.xml中开启二级缓存(默认已开启,可省略):

<settings><setting name="cacheEnabled" value="true"/> <!-- 全局二级缓存开关 -->
</settings>
3.2.2 Mapper接口开启缓存

在需要使用二级缓存的Mapper XML中添加<cache>标签:

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper"><!-- 开启二级缓存 --><cache eviction="LRU" <!-- 淘汰策略:LRU(最近最少使用) -->flushInterval="60000" <!-- 自动刷新间隔(毫秒,60秒) -->size="1024" <!-- 最大缓存条目 -->readOnly="false"/> <!-- 是否只读(false:缓存对象副本) --><!-- 查询语句(默认使用二级缓存) --><select id="selectById" resultType="User">SELECT id, username, age FROM user WHERE id = #{id}</select>
</mapper>

<cache>标签属性说明:

  • eviction:缓存淘汰策略(LRU:移除最近最少使用;FIFO:先进先出);
  • flushInterval:缓存自动刷新时间(毫秒,0表示不自动刷新);
  • size:最大缓存数量(过多会占用内存);
  • readOnlytrue(返回缓存对象本身,性能好但线程不安全);false(返回副本,安全但性能略低)。
3.2.3 实体类序列化(必须)

二级缓存可能将对象写入磁盘(如使用第三方缓存),因此实体类需实现Serializable接口:

// 实现Serializable接口
public class User implements Serializable {private Integer id;private String username;private Integer age;// getter/setter
}

3.3 实战案例:二级缓存演示

// 第一个SqlSession
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper userMapper1 = sqlSession1.getMapper(UserMapper.class);
User user1 = userMapper1.selectById(1);
sqlSession1.close(); // 关闭会话,将一级缓存写入二级缓存// 第二个SqlSession
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper userMapper2 = sqlSession2.getMapper(UserMapper.class);
// 命中二级缓存(无需查询数据库)
User user2 = userMapper2.selectById(1); 
sqlSession2.close();System.out.println(user1 == user2); // false(二级缓存返回副本,readOnly=false时)
System.out.println(user1.getId().equals(user2.getId())); // true(数据一致)

3.4 二级缓存的特点与适用场景

特点说明
手动开启需要在Mapper中配置<cache>标签
跨会话共享同一Mapper的所有SqlSession可共享缓存
支持序列化可配置第三方缓存(如Redis)持久化缓存

适用场景

  • 多会话共享的高频查询(如商品分类、字典表等不常变化的数据);
  • 读多写少的场景(避免频繁更新导致缓存失效)。

四、二级缓存的高级配置

4.1 禁用特定查询的二级缓存

若某查询不需要使用二级缓存(如实时性要求高的数据),可通过useCache="false"禁用:

<select id="selectLatestOrder" resultType="Order" useCache="false">SELECT * FROM `order` ORDER BY create_time DESC LIMIT 1
</select>

4.2 强制刷新二级缓存

若需在查询时强制刷新缓存(忽略现有缓存,重新查询数据库并更新缓存),可使用flushCache="true"

<select id="selectUserWithForceRefresh" resultType="User" flushCache="true">SELECT * FROM user WHERE id = #{id}
</select>

4.3 整合第三方缓存(如Redis)

MyBatis的默认二级缓存是内存缓存(重启后失效),生产环境通常整合Redis等分布式缓存,实现缓存持久化和分布式共享。

4.3.1 引入依赖(Redis+MyBatis-Redis)
<dependency><groupId>org.mybatis.caches</groupId><artifactId>mybatis-redis</artifactId><version>1.0.0-beta2</version>
</dependency>
<dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId><version>3.7.0</version>
</dependency>
4.3.2 配置Redis缓存
<!-- UserMapper.xml:指定缓存实现为Redis -->
<cache type="org.mybatis.caches.redis.RedisCache"><property name="host" value="localhost"/> <!-- Redis主机 --><property name="port" value="6379"/> <!-- Redis端口 --><property name="timeout" value="30000"/> <!-- 超时时间 --><property name="expiration" value="3600000"/> <!-- 缓存过期时间(毫秒) -->
</cache>

优势

  • 缓存持久化(应用重启后缓存不丢失);
  • 分布式共享(多实例应用共享缓存);
  • 支持缓存过期策略(自动清理过期数据)。

五、缓存使用的常见问题与避坑指南

5.1 一级缓存导致的脏读问题

问题:多SqlSession场景下,一级缓存可能读取到旧数据。

// SqlSession1查询数据并缓存
SqlSession sqlSession1 = sqlSessionFactory.openSession();
User user1 = sqlSession1.getMapper(UserMapper.class).selectById(1);// SqlSession2更新数据并提交
SqlSession sqlSession2 = sqlSessionFactory.openSession();
sqlSession2.getMapper(UserMapper.class).updateAge(1, 26);
sqlSession2.commit();
sqlSession2.close();// SqlSession1再次查询(一级缓存未更新,读取到旧数据)
User user2 = sqlSession1.getMapper(UserMapper.class).selectById(1);
System.out.println(user2.getAge()); // 25(旧值,而非更新后的26)

解决方案

  • 避免长生命周期的SqlSession(如Web应用中,一个请求对应一个SqlSession);
  • 对实时性要求高的查询,禁用一级缓存(通过flushCache="true");
  • 使用二级缓存(跨SqlSession共享,更新后会同步)。

5.2 二级缓存的序列化问题

错误:实体类未实现Serializable接口,二级缓存报错NotSerializableException

解决方案

  • 确保所有存入二级缓存的实体类实现Serializable接口;
  • 若使用第三方缓存(如Redis),需保证对象可被序列化(如避免循环引用)。

5.3 缓存与事务的一致性问题

问题:事务未提交时,其他SqlSession可能读取到未提交的缓存数据(脏读)。

原因

  • 一级缓存在事务内生效,未提交的更新不会刷新其他SqlSession的缓存;
  • 二级缓存仅在事务提交后才更新,避免此问题。

解决方案

  • 优先使用二级缓存(事务提交后才写入,保证数据一致性);
  • 关键业务(如支付)避免依赖缓存,直接查询数据库。

5.4 过度使用缓存导致内存溢出

问题:缓存大量数据(如全表查询结果),导致JVM内存溢出(OOM)。

解决方案

  • 限制缓存大小(size属性,如size="1024");
  • 设置缓存过期时间(flushInterval);
  • 避免缓存大集合(如分页查询,只缓存当前页数据);
  • 使用分布式缓存(如Redis),利用外部内存存储缓存。

总结:缓存机制的最佳实践
MyBatis的两级缓存各有适用场景,合理使用能显著提升性能,核心实践原则

  1. 一级缓存
  • 无需额外配置,充分利用其默认特性;
  • 注意SqlSession的生命周期(一个请求一个SqlSession),避免脏读;
  • 增删改操作后,一级缓存会自动清空,无需手动处理。
  1. 二级缓存
  • 仅对“读多写少、实时性要求低”的数据开启(如字典表、商品分类);
  • 必须实现实体类序列化,避免缓存失败;
  • 生产环境推荐整合Redis等分布式缓存,支持持久化和分布式共享;
  • 对实时性要求高的查询(如订单状态),禁用二级缓存。
  1. 通用原则
  • 缓存粒度越小越好(优先缓存单条数据,而非全表);
  • 避免缓存大对象和频繁变化的数据;
  • 结合监控工具(如MyBatis Log)分析缓存命中率,优化缓存策略。

若这篇内容帮到你,动动手指支持下!关注不迷路,干货持续输出!
ヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノヾ(´∀ ˋ)ノ

http://www.dtcms.com/a/287049.html

相关文章:

  • uniapp中报错:ReferenceError: FormData is not defined
  • 【安卓笔记】RxJava的Hook机制,整体拦截器
  • JavaScript空值安全深度指南
  • 加线机 和 胶带机
  • LVS 集群技术实践:NAT 与 DR 模式的配置与对比
  • 如何在HTML5页面中嵌入视频
  • 基于单片机宠物喂食器/智能宠物窝/智能饲养
  • 车载传统ECU---MCU软件架构设计指南
  • MSTP 多生成树协议
  • 零基础学后端-PHP语言(第一期-PHP环境配置)
  • 题解:CF1690G Count the Trains
  • 【C++基础】--多态
  • PortSwigger Labs 之 点击劫持利用
  • Go语言流程控制(if / for)
  • 编程研发工作日记_廖万忠_2016_2017
  • 从零构建监控系统:先“完美设计”还是先“敏捷迭代”?
  • Java Web项目Dump文件分析指南
  • 白话深度学习:一副PPT入门CNN,ResNet和Transformer
  • 闲庭信步使用图像验证平台加速FPGA的开发:第二十三课——图像直方图和灰度图像叠加的FPGA实现
  • 14-链路聚合
  • ZeroMQ中的REQ/REP模式:分布式系统的同步调用之道
  • JavaSE -- 数据操作流
  • 比亚迪古德伍德亮相:从技术突破到文化对话
  • 【53】MFC入门到精通——MFC串口助手(二)---通信版(发送数据 、发送文件、数据转换、清空发送区、打开/关闭文件),附源码
  • SDIO协商,枚举,CMD等概念
  • SSM框架——Day4
  • 文件管理-文件控制块和索引节点
  • 深入解析Linux文件描述符:原理、机制与应用实践
  • 光伏系统遮挡分析与设计优化策略
  • 网络基础12--可靠性概述及要求