资讯动态

MyBatis缓存机制学习

发布时间:2026/8/15 1:18:35 来源:尧图企业网站定制
一、MyBatis 缓存概述与Hibernate一样MyBatis 同样提供了一级缓存和二级缓存的支持。一级缓存: 基于PerpetualCache 的 HashMap本地缓存其存储作用域为 Session当 Session flush 或 close 之后该Session中的所有 Cache 就将清空。二级缓存: 与一级缓存其机制相同默认也是采用 PerpetualCacheHashMap存储不同在于其存储作用域为 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);/code2.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(); }代码执行结果分析第一次查询会访问数据库查询结果被存入一级缓存。第二次查询相同条件直接从一级缓存获取不会访问数据库。更新操作执行session.update()后MyBatis会自动清空该SqlSession的一级缓存。第三次查询由于缓存已被清空会重新访问数据库查询数据。第四次查询提交事务后查询结果再次被缓存。关键点说明一级缓存是基于SqlSession的同一个SqlSession内的相同查询会利用缓存。任何增删改操作insert、update、delete都会导致该SqlSession的一级缓存被清空。调用session.clearCache()方法也会手动清空一级缓存。关闭SqlSessionsession.close()后该Session的所有缓存都会被清除。这个示例清晰地展示了MyBatis一级缓存的自动失效机制当执行更新操作后即使是在同一个SqlSession中后续的相同查询也会因为缓存失效而重新访问数据库确保数据的一致性。三、二级缓存详解3.1 二级缓存配置Mybatis二级缓存测试mybaits的二级缓存是mapper范围级别除了在SqlMapConfig.xml设置二级缓存的总开关还要在具体的mapper.xml中开启二级缓存。3.1.1 全局配置在核心配置文件SqlMapConfig.xml中加入settingnamecacheEnabledvaluetrue/ !-- 全局配置参数需要时再设置 -- settings !-- 开启二级缓存 默认值为true -- settingnamecacheEnabledvaluetrue/ /settings3.1.2 Mapper配置开启二级缓存在userMapper.xml文件中添加如下配置mapper namespaceme.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(); /*一定要提交事务之后二级缓存才会起作用因为二级缓存是从cachemapper.xml中定义的cache中取得 如果session不commit那么数据就不会放入cache中*/ ystem.out.println(useruser); //由于使用的是两个不同的SqlSession对象所以即使查询条件相同一级缓存也不会开启使用 user session2.selectOne(statement, 1); //session2.commit(); System.out.println(user2user);3.3 缓存标签属性详解前面在使用cache/时我们说全部使用默认的配置所以仅仅写了一个标签而已现在我们来看这个标签包含那些属性。看如下例子,即一个常用的cache标签属性:cache evictionFIFO !--回收策略为先进先出-- flushInterval60000 !--自动刷新时间60s-- size512 !--最多缓存512个引用对象-- readOnlytrue/ !--只读--3.3.1 eviction(回收策略)LRU– 最近最少使用的:移除最长时间不被使用的对象。默认的属性FIFO– 先进先出:按对象进入缓存的顺序来移除它们。SOFT– 软引用:移除基于垃圾回收器状态和软引用规则的对象。WEAK– 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。3.3.2 flushInterval刷新间隔可以被设置为任意的正整数而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置也就是没有刷新间隔缓存仅仅调用语句时刷新。3.3.3 size引用数目可以被设置为任意正整数要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。3.3.4 readOnly只读可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝通过序列化。这会慢一些但是安全因此默认是false。四、MyBatis整合EhCache4.1 EhCache简介mybatis整合ehcacheehcache是一个分布式缓存框架。EhCache 是一个纯Java的进程内缓存框架是一种广泛使用的开源Java分布式缓存具有快速、精干等特点是Hibernate中默认的CacheProvider。 —《百度百科》4.2 分布式缓存需求分布缓存我们系统为了提高系统并发性能、一般对系统进行分布式部署集群部署方式不使用分布缓存缓存的数据在各各服务单独存储不方便系统开发。所以要使用分布式缓存对缓存数据进行集中管理。mybatis无法实现分布式缓存需要和其它分布式缓存框架进行整合。mybatis提供了一个cache接口如果要实现自己的缓存逻辑实现cache接口开发即可。mybatis和ehcache整合mybatis和ehcache整合包中提供了一个cache接口的实现类。4.3 整合步骤4.3.1 添加依赖在项目中加入如下两个jar包:dependency groupIdnet.sf.ehcache/groupId artifactIdehcache/artifactId version2.10.1/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-ehcache/artifactId version1.0.0/version /dependency4.3.2 配置Mapper整合ehcache配置mapper中cache中的type为ehcache对cache接口的实现类型。mapper namespaceUserMapper !-- 开启本mappernamespace下的二级缓存 type指定cache接口实现类mybatis默认使用PerpetualCache 要和eache整合需要配置type为ehcahe实现cache接口的类型 -- cachetypeorg.mybatis.caches.ehcache.EhcacheCache /cache4.3.3 调整缓存参数可以根据需求调整缓存参数cache typeorg.mybatis.caches.ehcache.EhcacheCache propertynametimeToIdleSecondsvalue3600/ propertynametimeToLiveSecondsvalue3600/ !-- 同ehcache参数maxElementsInMemory-- propertynamemaxEntriesLocalHeapvalue1000/ !-- 同ehcache参数maxElementsOnDisk -- propertynamemaxEntriesLocalDiskvalue10000000/ propertynamememoryStoreEvictionPolicyvalueLRU/ /cache4.3.4 配置EhCache文件加入ehcache的配置文件在classpath下配置ehcache.xmlehcache xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:noNamespaceSchemaLocation../config/ehcache.xsd diskStorepath/home/wang/cache/ defaultCache maxElementsInMemory1000 maxElementsOnDisk10000000 eternalfalse overflowToDiskfalse timeToIdleSeconds120 timeToLiveSeconds120 diskExpiryThreadIntervalSeconds120 memoryStoreEvictionPolicyLRU /defaultCache /ehcache属性说明diskStore指定数据在磁盘中的存储位置。defaultCache当借助CacheManager.add(‘demoCache’)创建Cache时EhCache便会采用defalutCache/指定的的管理策略以下属性是必须的maxElementsInMemory - 在内存中缓存的element的最大数目maxElementsOnDisk - 在磁盘上缓存的element的最大数目若是0表示无穷大eternal - 设定缓存的elements是否永远不过期。如果为true则缓存的数据始终有效如果为false那么还要根据timeToIdleSecondstimeToLiveSeconds判断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 定期自动清空缓存并重新加载数据平衡数据新鲜度与性能。注意事项确保缓存的数据对象实现了Serializable接口。在涉及多表关联查询时需要谨慎配置缓存避免因关联表数据更新导致缓存不一致。分布式部署环境下需结合 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 二级缓存更适用于数据更新频率极低的静态数据。对数据实时性要求不高的后台统计分析。单机或小规模应用且业务逻辑简单的场景。总结建议对于需要细粒度缓存控制、高实时性要求或分布式部署的生产系统建议禁用 MyBatis 自带的二级缓存设置cacheEnabledfalse。在业务层引入 Redis 等分布式缓存根据业务需求实现更精细的缓存策略如按商品 ID 缓存、设置不同的过期时间等。对于特别复杂的查询可考虑使用 Caffeine、Guava Cache 等本地缓存作为二级缓存的补充但需注意数据一致性问题。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价