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

MyBatis缓存机制学习

一、MyBatis 缓存概述

与Hibernate一样,MyBatis 同样提供了一级缓存和二级缓存的支持。

  • 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
  • 二级缓存: 与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义第三方存储源,如 Ehcache框架等。
  • 缓存更新机制: 当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。

二、一级缓存详解

2.1 一级缓存特性

MyBatis中一级缓存是默认开启的,即在查询中(一次SqlSession中)。只要当SqlSession不关闭,那么你的操作会默认存储使用一级缓存。

2.2 一级缓存测试

Mybatis一级缓存测试

String config = "sqlMapConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(config); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); SqlSession session = sqlSessionFactory.openSession(); // 执行在bean配置文件中定义的sql语句 User user = session.selectOne("UserMapper.findById", 1); System.out.println(user); /* * 一级缓存默认就会被使用 */ user = session.selectOne("UserMapper.findById", 1); System.out.println(user); session.close(); /* 1. 必须是同一个Session,如果session对象已经close()过了就不可能用了 */ session = MyBatisUtil.getSqlSession(); user = session.selectOne("UserMapper.findById", 1); System.out.println(user); /* 2. 查询条件必须是一样的 */ user = session.selectOne("UserMapper.findById", 2); System.out.println(user); /* 3. 没有执行过session.clearCache()清理缓存 */ //session.clearCache(); user = session.selectOne("UserMapper.findById", 2); System.out.println(user); /* 4. 没有执行过增删改的操作(这些操作都会清理缓存) */ session.update("UserMapper.updateUser", new User(2, "user", 23)); user = session.selectOne("UserMapper.findById", 2); System.out.println(user);</code>

2.3 一级缓存失效场景实战示例

以下是一个具体的一级缓存失效场景的实战代码示例,展示了在同一个 SqlSession 中,先执行查询,然后执行更新操作,再执行相同查询时,缓存失效并重新查询数据库的过程:

// 获取SqlSession SqlSession session = sqlSessionFactory.openSession(); try { // 第一次查询:查询ID为1的用户 System.out.println("=== 第一次查询(会查询数据库) ==="); User user1 = session.selectOne("UserMapper.findById", 1); System.out.println("第一次查询结果:" + user1); // 此时查询结果会被缓存到一级缓存中 // 第二次查询:相同条件,相同SqlSession System.out.println("\n=== 第二次查询(从一级缓存获取) ==="); User user2 = session.selectOne("UserMapper.findById", 1); System.out.println("第二次查询结果:" + user2); System.out.println("user1 == user2: " + (user1 == user2)); // true,说明是同一个对象,来自缓存 // 执行更新操作 System.out.println("\n=== 执行更新操作 ==="); User updateUser = new User(1, "updatedName", 30); int affectedRows = session.update("UserMapper.updateUser", updateUser); System.out.println("更新影响行数:" + affectedRows); // MyBatis在执行更新操作后会自动清空一级缓存 // 第三次查询:相同条件,相同SqlSession,但在更新操作之后 System.out.println("\n=== 第三次查询(更新后,缓存失效,重新查询数据库) ==="); User user3 = session.selectOne("UserMapper.findById", 1); System.out.println("第三次查询结果:" + user3); System.out.println("user1 == user3: " + (user1 == user3)); // false,说明是新对象,来自数据库 // 提交事务 session.commit(); // 第四次查询:提交事务后,相同条件 System.out.println("\n=== 第四次查询(提交事务后) ==="); User user4 = session.selectOne("UserMapper.findById", 1); System.out.println("第四次查询结果:" + user4); } finally { // 关闭SqlSession session.close(); }

代码执行结果分析:

  1. 第一次查询:会访问数据库,查询结果被存入一级缓存。
  2. 第二次查询:相同条件,直接从一级缓存获取,不会访问数据库。
  3. 更新操作:执行session.update()后,MyBatis会自动清空该SqlSession的一级缓存。
  4. 第三次查询:由于缓存已被清空,会重新访问数据库查询数据。
  5. 第四次查询:提交事务后,查询结果再次被缓存。

关键点说明:

  • 一级缓存是基于SqlSession的,同一个SqlSession内的相同查询会利用缓存。
  • 任何增删改操作(insert、update、delete)都会导致该SqlSession的一级缓存被清空。
  • 调用session.clearCache()方法也会手动清空一级缓存。
  • 关闭SqlSession(session.close())后,该Session的所有缓存都会被清除。

这个示例清晰地展示了MyBatis一级缓存的自动失效机制:当执行更新操作后,即使是在同一个SqlSession中,后续的相同查询也会因为缓存失效而重新访问数据库,确保数据的一致性。

三、二级缓存详解

3.1 二级缓存配置

Mybatis二级缓存测试

mybaits的二级缓存是mapper范围级别,除了在SqlMapConfig.xml设置二级缓存的总开关,还要在具体的mapper.xml中开启二级缓存。

3.1.1 全局配置

在核心配置文件SqlMapConfig.xml中加入:

<settingname='cacheEnabled'value='true'/> <!-- 全局配置参数,需要时再设置 --> <settings> <!-- 开启二级缓存 默认值为true --> <settingname='cacheEnabled'value='true'/> </settings>
3.1.2 Mapper配置

开启二级缓存,在userMapper.xml文件中添加如下配置:

<mapper namespace="me.gacl.mapping.userMapper"> <!-- 开启二级缓存这个必须开启,全部使用默认的配置--> <cache/>

3.2 二级缓存测试代码

具体的测试代码:

/* * 测试二级缓存 * 使用两个不同的SqlSession对象去执行相同查询条件的查询,第二次查询时不会再发送SQL语句,而是直接从缓存中取出数据 */ String statement = "UserMapper.findById"; SqlSessionFactory factory = MyBatisUtil.getSqlSessionFactory(); //开启两个不同的SqlSession SqlSession session1 = factory.openSession(); SqlSession session2 = factory.openSession(); //使用二级缓存时,User类必须实现一个Serializable接口===> User implements Serializable User user = session1.selectOne(statement, 1); session1.commit(); /*一定要提交事务之后二级缓存才会起作用,因为二级缓存是从cache(mapper.xml中定义的cache)中取得 如果session不commit,那么,数据就不会放入cache中*/ ystem.out.println("user="+user); //由于使用的是两个不同的SqlSession对象,所以即使查询条件相同,一级缓存也不会开启使用 user = session2.selectOne(statement, 1); //session2.commit(); System.out.println("user2="+user);

3.3 缓存标签属性详解

前面在使用<cache/>时我们说全部使用默认的配置,所以仅仅写了一个标签而已,现在我们来看这个标签包含那些属性。

看如下例子,即一个常用的cache标签属性:

<cache eviction="FIFO" <!--回收策略为先进先出--> flushInterval="60000" <!--自动刷新时间60s--> size="512" <!--最多缓存512个引用对象--> readOnly="true"/> <!--只读-->
3.3.1 eviction(回收策略)
  1. LRU– 最近最少使用的:移除最长时间不被使用的对象。(默认的属性)
  2. FIFO– 先进先出:按对象进入缓存的顺序来移除它们。
  3. SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。
  4. WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
3.3.2 flushInterval(刷新间隔)

可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。

3.3.3 size(引用数目)

可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。

3.3.4 readOnly(只读)

可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。

四、MyBatis整合EhCache

4.1 EhCache简介

mybatis整合ehcache

ehcache是一个分布式缓存框架。

EhCache 是一个纯Java的进程内缓存框架,是一种广泛使用的开源Java分布式缓存,具有快速、精干等特点,是Hibernate中默认的CacheProvider。 —《百度百科》

4.2 分布式缓存需求

分布缓存

我们系统为了提高系统并发,性能、一般对系统进行分布式部署(集群部署方式)

不使用分布缓存,缓存的数据在各各服务单独存储,不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理。

mybatis无法实现分布式缓存,需要和其它分布式缓存框架进行整合。

mybatis提供了一个cache接口,如果要实现自己的缓存逻辑,实现cache接口开发即可。

mybatis和ehcache整合,mybatis和ehcache整合包中提供了一个cache接口的实现类。

4.3 整合步骤

4.3.1 添加依赖

在项目中加入如下两个jar包:

<dependency> <groupId>net.sf.ehcache</groupId> <artifactId>ehcache</artifactId> <version>2.10.1</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-ehcache</artifactId> <version>1.0.0</version> </dependency>
4.3.2 配置Mapper

整合ehcache

配置mapper中cache中的type为ehcache对cache接口的实现类型。

<mapper namespace='UserMapper'> <!-- 开启本mappernamespace下的二级缓存 type:指定cache接口实现类,mybatis默认使用PerpetualCache 要和eache整合,需要配置type为ehcahe实现cache接口的类型 --> <cachetype='org.mybatis.caches.ehcache.EhcacheCache'> </cache>
4.3.3 调整缓存参数

可以根据需求调整缓存参数:

<cache type='org.mybatis.caches.ehcache.EhcacheCache'> <propertyname='timeToIdleSeconds'value='3600'/> <propertyname='timeToLiveSeconds'value='3600'/> <!-- 同ehcache参数maxElementsInMemory--> <propertyname='maxEntriesLocalHeap'value='1000'/> <!-- 同ehcache参数maxElementsOnDisk --> <propertyname='maxEntriesLocalDisk'value='10000000'/> <propertyname='memoryStoreEvictionPolicy'value='LRU'/> </cache>
4.3.4 配置EhCache文件

加入ehcache的配置文件

在classpath下配置ehcache.xml

<ehcache xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xsi:noNamespaceSchemaLocation='../config/ehcache.xsd'> <diskStorepath='/home/wang/cache'/> <defaultCache maxElementsInMemory='1000' maxElementsOnDisk='10000000' eternal='false' overflowToDisk='false' timeToIdleSeconds='120' timeToLiveSeconds='120' diskExpiryThreadIntervalSeconds='120' memoryStoreEvictionPolicy='LRU'> </defaultCache> </ehcache>

属性说明:

  • diskStore:指定数据在磁盘中的存储位置。

  • defaultCache:当借助CacheManager.add(‘demoCache’)创建Cache时,EhCache便会采用<defalutCache/>指定的的管理策略

以下属性是必须的:

  • maxElementsInMemory - 在内存中缓存的element的最大数目

  • maxElementsOnDisk - 在磁盘上缓存的element的最大数目,若是0表示无穷大

  • eternal - 设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断

  • overflowToDisk- 设定当内存缓存溢出的时候是否将过期的element缓存到磁盘上

以下属性是可选的:

  • timeToIdleSeconds - 当缓存在EhCache中的数据前后两次访问的时间超过
  • timeToIdleSeconds的属性取值时,这些数据便会删除,默认值是0,也就是可闲置时间无穷大

  • timeToLiveSeconds - 缓存element的有效生命期,默认是0.,也就是element存活时间无穷大

  • diskSpoolBufferSizeMB 这个参数设置DiskStore(磁盘缓存)的缓存区大小.默认是30MB.每个Cache都应该有自己的一个缓冲区.

  • diskPersistent在VM重启的时候是否启用磁盘保存EhCache中的数据,默认是false。

  • diskExpiryThreadIntervalSeconds - 磁盘缓存的清理线程运行间隔,默认是120秒。每个120s,相应的线程会进行一次EhCache中数据的清理工作

  • memoryStoreEvictionPolicy - 当内存缓存达到最大,有新的element加入的时候,移除缓存中element的策略。默认是LRU(最近最少使用),可选的有LFU(最不常使用)和FIFO(先进先出)

五、二级缓存应用场景

MyBatis 二级缓存适用于以下典型场景,能够有效降低数据库访问压力,提升系统性能:

  • 读多写少的查询:数据更新频率低,但查询请求量大的场景。例如:历史订单查询、商品分类信息、静态配置数据等。
  • 对实时性要求不高的统计分析:复杂的统计报表、数据分析 SQL 执行耗时较长,且数据变化周期较长(如每日、每周统计)。
  • 公共基础数据:如地区编码、字典表、系统参数等几乎不变的数据。
  • 用户非敏感数据查询:如文章列表、公告信息、电话账单查询等,允许一定时间的数据延迟。

实现方式:通过配置<cache>标签的flushInterval属性,设置合理的缓存刷新间隔(如 30 分钟、1 小时、24 小时),让 MyBatis 定期自动清空缓存并重新加载数据,平衡数据新鲜度与性能。

注意事项

  1. 确保缓存的数据对象实现了Serializable接口。
  2. 在涉及多表关联查询时,需要谨慎配置缓存,避免因关联表数据更新导致缓存不一致。
  3. 分布式部署环境下,需结合 Redis、Ehcache 等分布式缓存框架,避免各节点缓存数据不一致。

六、二级缓存局限性

尽管 MyBatis 二级缓存能显著提升查询性能,但在实际应用中存在以下局限性,需要开发者根据业务场景权衡使用:

6.1 缓存粒度粗,更新影响范围大

MyBatis 二级缓存以 Mapper 的 namespace 为单位进行缓存。当某个 Mapper 中的任意一条数据发生增、删、改操作时,该 namespace 下的所有缓存数据都会被清空。例如:

  • 商品信息表有 10000 条记录,全部被缓存。
  • 当仅更新其中一件商品的价格时,整个商品表的缓存都会被清除。
  • 后续查询所有商品(包括未修改的 9999 件)都会重新访问数据库,造成“缓存雪崩”效应。

6.2 分布式环境缓存不一致

在集群部署环境下,MyBatis 自带的二级缓存是进程内缓存,不同服务器节点之间的缓存数据无法共享。这会导致:

  • 节点 A 更新了数据,清除了自己的缓存。
  • 节点 B 的缓存中仍然是旧数据,用户从节点 B 访问会得到过期结果。

解决方案:必须整合 Redis、Memcached、Ehcache 等分布式缓存框架,实现缓存数据的集中管理。

6.3 多表关联查询缓存管理复杂

当查询涉及多个表时,缓存配置变得复杂:

  • 需要在关联的多个 Mapper 中分别配置<cache-ref>来共享同一个缓存空间。
  • 任何一个关联表的数据更新,都会导致整个关联查询结果集缓存失效。
  • 难以实现细粒度的缓存更新策略。

6.4 事务提交后才生效

二级缓存生效的前提是事务必须提交session.commit())。如果查询后未提交事务,数据不会放入二级缓存,其他 Session 也无法从缓存中获取数据。这要求开发者必须显式管理事务边界。

6.5 适用场景有限

由于上述限制,MyBatis 二级缓存更适用于:

  • 数据更新频率极低的静态数据。
  • 对数据实时性要求不高的后台统计分析。
  • 单机或小规模应用,且业务逻辑简单的场景。

总结建议:对于需要细粒度缓存控制、高实时性要求或分布式部署的生产系统,建议:

  1. 禁用 MyBatis 自带的二级缓存(设置cacheEnabled=false)。
  2. 业务层引入 Redis 等分布式缓存,根据业务需求实现更精细的缓存策略(如按商品 ID 缓存、设置不同的过期时间等)。
  3. 对于特别复杂的查询,可考虑使用 Caffeine、Guava Cache 等本地缓存作为二级缓存的补充,但需注意数据一致性问题。
http://www.jsqmd.com/news/1395119/

相关文章:

  • 2026年8月重庆市合川区移动300M单宽带避坑攻略 - 找卡家园
  • 猫抓资源嗅探扩展快速上手指南:网页里的视频音频,一次点击就能保存
  • trackerslist公共Tracker列表:114个免费Tracker,3步让BT下载告别龟速
  • Windows系统文件srpapi.dll丢失找不到问题解决
  • Unity第三人称角色控制器:从Rigidbody物理驱动到动画状态机实战
  • 2026年8月青岛市黄岛区移动1000M宽带一篇说透怎么选 - 找卡家园
  • 不装模拟器也能玩安卓应用?APK Installer 安装器完整上手指南
  • 非线性最小二乘与匈牙利算法在无人机无源定位与编队调整中的应用
  • 2026年8月龙岩市移动1000M宽带小白避坑指南 - 找卡家园
  • PostgreSQL重启异常排查指南:从进程架构到实战修复
  • 源纹天书》第三百零一章至第三百零五章:视野到行动的距离、规则重组的触发、始世界的响应、准圣境的临门一脚、迈向圣人境的第一步!
  • 2026年8月邢台市威县移动1000M宽带办理避坑指南 - 找卡家园
  • Windows系统文件sspicli.dll丢失找不到问题解决
  • 2026年8月太原市晋源区联通1000M宽带避坑指南一篇说透 - 找卡家园
  • MyBatis的resultType和resultMap的区别
  • Python+Premiere自动化剪辑:批量生成带货视频实战
  • 知网AIGC检测和查重,BunnyScholar一次都搞定
  • Java日志框架实战指南:从SLF4J、Log4j2到生产环境配置
  • 2026年8月南京市玄武区联通500M宽带办理避坑指南 - 找卡家园
  • LeetCode 430:多级双向链表扁平化算法详解与工程实践
  • 从通用AI到专属副驾:如何通过技能库打造更懂你的智能助手
  • 墙面翻新价格差异与避坑指南 - 行业洞察分析师
  • 2026年想选靠谱TPR 宁波叮咚新材料有限责任公司值得考虑 - 起跑123
  • 2026年8月荆门市钟祥市移动1000M宽带怎么选怎么办才靠谱 - 找卡家园
  • AI 驱动欺诈风险下人力要素在金融反欺诈中的核心支撑作用研究
  • 光伏缺陷检测从0到1:PVEL-AD数据集3步跑通质检模型
  • Java中信号量(Semaphore):从本地到分布式
  • 2026年8月湘潭市雨湖区移动1000M宽带申请避坑与实测攻略 - 找卡家园
  • 论文AI率多少算合格,用BunnyScholar自查最省心
  • 2026年8月临沂市罗庄区移动200M单宽带申请避坑与实测攻略 - 找卡家园