![cover](https://img-blog.csdnimg.cn/img_convert/1f20816e15694cf3b56262c1f87e2a14.png)
MyBatis系统学习篇 - MyBatis的缓存
MyBatis的缓存实现原理主要基于三级缓存机制,包括一级缓存(本地缓存)、二级缓存(全局缓存)和三级缓存(跨会话缓存)。这个缓存在我们实际开发中可以避免我们查询重复的数据,在一定程度上可以帮助我们减少对数据库同一数据的重复查询,也可以在一定程度上使用MyBatis缓存可以帮助我们更好的查询数据和进行数据交互,减少对数据库的数据查询次数吧。
MyBatis的缓存实现原理主要基于三级缓存机制,包括一级缓存(本地缓存)、二级缓存(全局缓存)和三级缓存(跨会话缓存)。这个缓存在我们实际开发中可以避免我们查询重复的数据,在一定程度上可以帮助我们减少对数据库同一数据的重复查询,也可以在一定程度上使用MyBatis缓存可以帮助我们更好的查询数据和进行数据交互,减少对数据库的数据查询次数吧。
一级缓存
MyBatis一级缓存也可以称作本地缓存,他是SqlSession级别的缓存,默认开启,可以减少我们对数据库的重复查询,当执行查询的时候,查询结果会被存储在SqlSession中的本地存储中,在同一个Session中,如果执行相同的查询,MyBatis会从本地缓存中查找结果,如果找到则直接返回,否则再去数据库中查询并存储到本地缓存中。
一级缓存的实现
// 在MyBatis配置文件中开启一级缓存
<configuration>
<settings>
<setting name="localCacheScope" value="SESSION"/>
</settings>
</configuration>
public static void main(String[] args) {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 测试一级缓存
Emp emp1 = empMapper.getEmpById(1); // 第一次查询,会从数据库中获取数据
Emp emp2 = empMapper.getEmpById(1); // 第二次查询,会从一级缓存中获取数据
System.out.println(emp1 == emp2); // 输出:true,说明从一级缓存中获取到了同一个对象
}
}
一级缓存失效的几种情况:
-
不同的SqlSession:一级缓存是基于
SqlSession
的,当使用不同的SqlSession
对象执行相同的查询时,一级缓存会失效。因为每个SqlSession
都有自己的本地缓存,无法共享缓存数据。 -
手动清空缓存:通过调用
SqlSession
的clearCache()
方法可以手动清空一级缓存。这样会导致之前缓存的数据被清除,下一次查询会重新从数据库中获取数据。 -
更新操作:当执行了插入、更新或删除操作时,可能会影响到缓存中的数据。MyBatis会自动将更新操作同步到缓存中,但是会清空相关的缓存数据,以保证缓存的数据与数据库的数据一致。
-
缓存大小限制:一级缓存的大小是有限的,默认情况下,一级缓存的大小为1024个对象。当缓存中的对象数量达到上限时,新的查询结果会导致最早的查询结果被淘汰出缓存,从而失效。
-
手动提交事务:如果在执行查询之前手动提交了事务(调用了
commit()
方法),则会导致一级缓存失效。因为事务提交后,会关闭当前的SqlSession
,同时清空一级缓存。
二级缓存
MyBatis二级缓存也可以被称作全局缓存,一般上是默认关闭的,并且配置方式也与一级缓存有区别,需要我们在具体的mapper映射文件中手动配置开启。
二级缓存开启的条件:
因为二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果被缓存,伺候再次执行相同的查询语句就可以直接从缓存中获取。
- 在核心配置文件中,设置全局配置属性
cacheEnable="true"
,默认true,一般不需要设置- 在映射文件中配置
<cache />
- 二级缓存必须在SqlSession关闭或者提交后有效
- 查询的数据所转换的实体类类型必须实现序列化的接口。
当我们开启二级缓存的时候,查询的结果会被存储在Mapper的全局缓存中,多个SqlSession可以共享同一个Mapper的二级缓存,在执行查询的时候,MyBatis会从我们的二级缓存中查询结果,如果找到则直接返回,如果没有找到,那么就会去数据库中查询,并存储到二级缓存中。
// 在Mapper接口对应的映射文件中开启二级缓存
<cache/>
public static void main(String[] args) {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 测试二级缓存
SqlSession sqlSession2 = sqlSessionFactory.openSession();
EmpMapper empMapper2 = sqlSession2.getMapper(EmpMapper.class);
Emp emp3 = empMapper2.getEmpById(1); // 第一次查询,会从数据库中获取数据
sqlSession2.close(); // 关闭SqlSession
SqlSession sqlSession3 = sqlSessionFactory.openSession();
EmpMapper empMapper3 = sqlSession3.getMapper(EmpMapper.class);
Emp emp4 = empMapper3.getEmpById(1); // 第二次查询,会从二级缓存中获取数据
sqlSession3.close();
System.out.println(emp3 == emp4); // 输出:true,说明从二级缓存中获取到了同一个对象
}
}
cache标签的配置属性值
-
eviction
:指定缓存的回收策略,用于决定在缓存达到上限时如何清理缓存。常用的回收策略包括:LRU
(Least Recently Used):最近最少使用,根据最近的访问时间来淘汰数据。FIFO
(First In, First Out):先进先出,根据数据最早进入缓存的时间来淘汰数据。SOFT
:软引用,根据JVM的垃圾回收机制来决定是否清理缓存数据。WEAK
:弱引用,类似于软引用,但更容易被垃圾回收器回收。
-
flushInterval
:指定缓存刷新间隔,表示缓存刷新的时间间隔,单位为毫秒。设置了该属性后,缓存会定期刷新,以保证缓存数据的有效性。 -
readOnly
:指定缓存是否为只读,如果设置为true,表示缓存数据不会被修改,可以提高缓存的性能。 -
size
:指定缓存的大小限制,表示缓存中可以存储的对象数量上限。当缓存中的对象数量达到该上限时,会根据回收策略进行数据清理。 -
type
:指定缓存的实现类型,可以是MyBatis提供的内置缓存实现,也可以是自定义的缓存实现。常用的内置缓存实现包括:PERPETUAL
:永久缓存,数据永久保存在缓存中。FIFO
:先进先出缓存。LRU
:最近最少使用缓存。SOFT
:软引用缓存。WEAK
:弱引用缓存。
示例配置:
<cache
eviction="LRU"
flushInterval="60000"
readOnly="false"
size="1024"
type="PERPETUAL"/>
MyBatis缓存查询顺序:
先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿出来使用,如果二级缓存没有命中,那就在查一级缓存,如果一级缓存也没有命中,那就查询数据库。SqlSession关闭后,以及缓存中的数据会写入到二级缓存中。
二级缓存失效的几种原因:
数据更新:当执行了插入、更新或删除操作时,会导致与这些操作相关的缓存数据失效。MyBatis会自动将更新操作同步到缓存中,但也会清空相关的缓存数据,以保证缓存中的数据与数据库的数据一致。
并发操作:在并发环境下,如果多个线程同时对同一条数据进行更新操作,可能会导致缓存中的数据与数据库中的数据不一致。这时需要考虑缓存的并发控制策略,以避免脏数据的产生。
查询结果不满足缓存条件:MyBatis的二级缓存对查询结果有一定的条件限制,例如查询结果的类型必须是可序列化的、不能包含动态SQL等。如果查询结果不满足这些条件,可能会导致缓存失效。
缓存大小限制:二级缓存的大小是有限的,当缓存中的对象数量达到上限时,新的查询结果会导致最早的查询结果被淘汰出缓存,从而失效。
手动清空缓存:通过调用SqlSession的clearCache()方法可以手动清空二级缓存。这样会导致之前缓存的数据被清除,下一次查询会重新从数据库中获取数据。
事务提交:如果在执行查询之前手动提交了事务(调用了commit()方法),则会导致二级缓存失效。因为事务提交后,会关闭当前的SqlSession,同时清空二级缓存。
三级缓存
三级缓存是一种跨会话级别的缓存,他通过与外部缓存系统,例如Redis,Memcached等进行实现。通过外部缓存系统的集成,进而实现多个应用实例之间的缓存共享,从而提高缓存的利用率和拓展性。
关于三级缓存,无法依靠MyBatis单独实现,因为他本事不提供直接对外缓存系统的集成,故而无法实现一个完整的三级缓存示例,然而,可以通过MyBatis的扩展插件或者自定义实现来集成外部缓存系统,例如:Redis,Memcached等。
我们采用Redis自定义实现Mybatis的三级缓存。
首先,需要引入MyBatis的扩展插件,例如MyBatis Redis Cache,它是一个MyBatis的缓存插件,可以将缓存数据存储到Redis中。
然后,需要在MyBatis的配置文件中配置该插件,指定Redis作为缓存的实现:
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
<typeAliases>
<!-- 定义需要缓存的实体类别名 -->
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<!-- 配置数据源信息 -->
</dataSource>
</environment>
</environments>
<mappers>
<!-- 配置Mapper接口 -->
</mappers>
<plugins>
<plugin interceptor="org.mybatis.caches.redis.RedisCache"/>
</plugins>
</configuration>
<plugin>
标签配置了MyBatis Redis Cache插件,将Redis作为缓存的实现。
接下来,可以在Java代码中进行测试,通过调用Mapper接口的方法来触发缓存的使用:
public class MyBatisCacheTest {
public static void main(String[] args) {
try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class);
// 调用Mapper接口的方法进行查询
Emp emp1 = empMapper.getEmpById(1); // 第一次查询,会从数据库中获取数据
Emp emp2 = empMapper.getEmpById(1); // 第二次查询,会从Redis缓存中获取数据
System.out.println(emp1 == emp2); // 输出:true,说明从Redis缓存中获取到了同一个对象
}
}
}
实际的三级缓存实现可能会更加复杂,涉及到缓存的清理、失效策略、缓存击穿和雪崩等问题,需要根据实际情况进行更加细致的配置和测试。
更多推荐
所有评论(0)