MyBatis缓存
本文最后更新于:2 年前
引言
在高并发和大数据量的应用场景中,缓存机制对于提升性能和降低数据库负担至关重要。MyBatis作为一个流行的持久层框架,提供了灵活的缓存配置选项,允许开发者根据具体需求实施一级缓存和二级缓存策略。此外,MyBatis还支持与第三方缓存框架如EhCache的集成,进一步扩展了缓存的功能和效率。本文将详细介绍这些缓存机制的工作原理和配置方法。
一级缓存(Session Cache)
简介
一级缓存是MyBatis中默认启用的缓存。它的作用范围限定于同一个SqlSession
内。当在同一个SqlSession
内执行相同的 SQL 查询时,结果会被缓存起来,并在下次查询相同SQL时直接从缓存返回结果,避免了对数据库的重复查询。
- 作用域:仅限于同一
SqlSession
。 - 生命周期:与
SqlSession
的生命周期相同。当SqlSession
关闭或执行非查询类SQL时,其一级缓存也会被清空。
配置
一级缓存是自动启用的,无需特别配置。
Demo
在多次查询中观察控制台日志输出(数据库隔离级别为可重复读)
1 |
|
运行程序,控制台输出日志如下
1 |
|
日志分析:
- 第1次和第2次使用相同的SqlSession,因此第2次查询直接从缓存中取数据。
- 第3次查询前发生了SqlSession更新相同行操作,因此查询没使用缓存,而第4次查询使用了缓存。
- 第5次查询前发生了SqlSession更新不同行操作,因此查询没使用缓存,而第6次查询使用了缓存。
- 第7次查询前发生了SqlSession更新其他表操作,因此查询没使用缓存,而第8次查询使用了缓存。
- 第9次查询前手动清除了SqlSession的缓存,因此查询没有使用缓存,而第10次查询使用了缓存。
- 第11次查询前SqlSession提交了一次事务,因此查询没有使用缓存,而第12次查询使用了缓存。
- 第13次查询前其他SqlSession发生了更新不同行的操作,但不影响本SqlSession,因此第13次和第14次查询都使用了缓存。
- 第15次查询前SqlSession回滚了事务,因此查询没使用缓存,而后不同SqlSession回滚了事务,但不影响本SqlSession,因此第16次查询使用了缓存。
- 注意这里隐藏了一个场景,其他SqlSession更新了相同行,因为更新相同行的条件是本SqlSession提交事务,否则MySQL的行锁会导致其他SqlSession更新相同行失败,因此结果跟本次SqlSession提交事务后的结果是一致的,即提交了事务后本次SqlSession的查询不使用缓存。
总结:在默认的一级缓存中,相同的事务中SqlSession只要执行了非查询类的操作(更新、删除、事务提交或回滚),就会导致本次事务中的所有缓存都失效,在可重复度的隔离级别下,其他事务不会影响本次事务,因此不会影响本次事务的缓存。
二级缓存(Mapper Cache)
简介
二级缓存的作用范围更广,可以跨多个SqlSession
和SqlMapper
。二级缓存需要显式地在MyBatis配置文件中或通过注解启用。启用后,多个会话可以重用缓存的数据,大大减少数据库的查询次数,提高了应用性能。
- 作用域:跨
SqlSession
,通常绑定到一个Mapper接口。 - 生命周期:通常跟应用的生命周期一致,或者是缓存提供者(如Ehcache、Redis)管理的。
- 配置:需要在MyBatis的配置文件中启用,并且可以对其细粒度控制,例如配置缓存的大小、清除策略、读写策略等。
配置
全局配置文件中开启(默认开启)
1
<setting name="cacheEnabled" value="true"/>
Mapper文件中加上
<cache/>
标签表示局部开启1
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
eviction:缓存的回收策略。默认为LRU策略。
LRU:Least Recently Used,最近最少使用。
FIFO:First In First Out,先进先出。
SOFT:软引用指向对象优先淘汰。
WEAK:弱引用指向对象优先淘汰。
flushInterval:二级缓存的刷新时间间隔,单位为毫秒,没有则代表不刷新缓存。
readOnly
true:多条相同语句执行返回的对象是共享的一个,性能高,可能有并发问题。
false:多条相同语句执行返回的对象是克隆出来的副本,性能一般,多线程安全。
size:引用数目,一个数值,表示缓存中可以存储的引用数量,默认1024。
DEMO
在多次查询中观察控制台日志输出(数据库隔离级别为可重复读)
1 |
|
运行结果如下
1 |
|
日志分析:
- 由于session1第1次查询后未提交或关闭session,因此session2的查询不是从二级缓存中取数据,而是从磁盘中取数据。
- session1提交、同时清除session2的一级缓存,因此session2的第2次查询从二级缓存中取数据。
- session2在第2次查询后就清除了一级缓存,第3次查询仍然是从缓存中取数据,因此可以判定缓存为二级缓存。
- session1更新其它的数据表并提交事务、session2重新开启事务,第4次查询仍然从二级缓存中取数据。
- session1更新相同表的其他行数据,未提交事务前,session2的第5次查询从二级缓存中取数据。
- session1提交事务后,二级缓存失效,session2的第6次查询重新从磁盘中读取数据。
- session2关闭,此时将session1的一级缓存清除,查询仍然从二级缓存中取数据,而对于刚才更新并提交事务的相同行,则会重新从磁盘中取数据。
总结:在原有一级缓存的规则下,其他session的事务提交或会话关闭会将其一级缓存写入到二级缓存中,且未有session提交此表相同行的事务的前提下,此表中该行的二级缓存会一直存在于SqlSessionFactory中。
集成EhCache
简介
EhCache是一个广泛使用的开源Java分布式缓存框架,提供了内存和磁盘存储的缓存功能,非常适合用作MyBatis的二级缓存。通过集成EhCache
,MyBatis可以提升应用性能,减少数据库访问次数,以及提供更复杂的缓存管理功能。
集成EcCache
可以替换MyBatis自带的二级缓存(一级缓存无法替代)。
DEMO
引入依赖
1
2
3
4
5<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-ehcache</artifactId>
<version>1.2.3</version>
</dependency>类的根路径下创建配置文件ehcache.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
updateCheck="false">
<!--磁盘存储:将缓存中暂时不使用的对象,转移到硬盘,类似于Windows系统的虚拟内存。-->
<diskStore path="e:/ehcache"/>
<!--defaultCache:默认的管理策略-->
<!--eternal:设定缓存的elements是否永远不过期。如果为true,则缓存的数据始终有效,如果为false那么还要根据timeToIdleSeconds,timeToLiveSeconds判断-->
<!--maxElementsInMemory:在内存中缓存的element的最大数目-->
<!--overflowToDisk:如果内存中数据超过内存限制,是否要缓存到磁盘上-->
<!--diskPersistent:是否在磁盘上持久化。指重启jvm后,数据是否有效。默认为false-->
<!--timeToIdleSeconds:对象空闲时间(单位:秒),指对象在多长时间没有被访问就会失效。只对eternal为false的有效。默认值0,表示一直可以访问-->
<!--timeToLiveSeconds:对象存活时间(单位:秒),指对象从创建到失效所需要的时间。只对eternal为false的有效。默认值0,表示一直可以访问-->
<!--memoryStoreEvictionPolicy:缓存的3 种清空策略-->
<!--FIFO:first in first out (先进先出)-->
<!--LFU:Less Frequently Used (最少使用).意思是一直以来最少被使用的。缓存的元素有一个hit 属性,hit 值最小的将会被清出缓存-->
<!--LRU:Least Recently Used(最近最少使用). (ehcache 默认值).缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存-->
<defaultCache eternal="false" maxElementsInMemory="1000" overflowToDisk="false" diskPersistent="false"
timeToIdleSeconds="0" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU"/>
</ehcache>在Mapper文件中指定使用
EhCache
1
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
运行测试程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
SqlSession session1 = sqlSessionFactory.openSession();
SqlSession session2 = sqlSessionFactory.openSession();
StudentMapper mapper1 = session1.getMapper(StudentMapper.class);
StudentMapper mapper2 = session2.getMapper(StudentMapper.class);
log.info("session1 第1次查询:{}", mapper1.getById(1L));
log.info("session2 第1次查询:{}", mapper2.getById(1L));
session1.close();
session2.clearCache();
log.info("session2 第2次查询:{}", mapper2.getById(1L));控制台输出如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2422:40:41.562 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
22:40:42.323 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1205559205.
22:40:42.323 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47db5fa5]
22:40:42.325 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
22:40:42.350 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
22:40:42.362 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
22:40:42.363 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
22:40:42.365 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
22:40:42.367 [main] INFO space.yangtao.client.StudentMapperTest - session1 第1次查询:StudentPO(id=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:40:42.367 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.0
22:40:42.367 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
22:40:42.381 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 367967231.
22:40:42.382 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@15eebbff]
22:40:42.382 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Preparing: select * from tt_student where id = ?
22:40:42.382 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - ==> Parameters: 1(Long)
22:40:42.383 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Columns: id, class_id, name, height, gender, birthday, create_time
22:40:42.383 [main] TRACE space.yangtao.mapper.StudentMapper.getById - <== Row: 1, 1, zhangsan1, 180.0, 男, 1990-01-01, 2022-07-01 22:10:22
22:40:42.384 [main] DEBUG space.yangtao.mapper.StudentMapper.getById - <== Total: 1
22:40:42.384 [main] INFO space.yangtao.client.StudentMapperTest - session2 第1次查询:StudentPO(id=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)
22:40:42.385 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47db5fa5]
22:40:42.385 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@47db5fa5]
22:40:42.385 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1205559205 to pool.
22:40:42.385 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.3333333333333333
22:40:42.385 [main] INFO space.yangtao.client.StudentMapperTest - session2 第2次查询:StudentPO(id=1, name=zhangsan1, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Fri Jul 01 22:10:22 CST 2022)由日志可知,MyBatis已集成并使用
EhCache
。
注意:建议提醒读者在实际应用中,需要根据应用的并发量和数据量,合理配置缓存大小,以防止内存溢出或缓存失效等问题。
总结
通过本文的学习,开发者可以充分理解并运用MyBatis提供的缓存机制,有效地管理SqlSession的生命周期和跨会话的数据共享。一级缓存带来了会话内的数据访问优化,而二级缓存则扩展了数据共享的范围,提高了应用的响应速度和数据处理能力。此外,通过EhCache的集成,开发者能够实现更复杂的缓存策略,如数据的持久化和过期策略管理。掌握这些技能将显著提升数据库操作的效率和应用的性能。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!