本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、Mybatis 缓存 - 一级缓存
一级缓存又叫做查询缓存,作用在
SqlSession中,在 Mybatis 中是默认开启的。
1.1 实践现象
List<User> users1 = userAnnoMapper.findAll();
users1.forEach(System.out::println);
System.out.println("------------------");
List<User> users2 = userAnnoMapper.findAll();
users2.forEach(System.out::println);
- 在同一个
SqlSession中的两次相同的查询。第一次查询首先会在一级缓存中查询是否执行过当前语句,没有执行过,则访问数据库获取数据,之后将数据缓存。第二次查询会在一级缓存中尝试查询,发现缓存中之前执行过,那么就直接从缓存中获取数据。 - 如果两次查询之间存在命令类操作(新增、修改、删除),则该操作会清空缓存,在第二次查询的时候就会走数据库查询。
- 清空
SqlSession的一级缓存的操作,目的是为了保证缓存数据总是最新的。
1.2 一级缓存原理
- 首先一级缓存本质上是
PerpetualCache中的一个 HashMap,这个类对外提供了操作缓存的方法。 BaseExecutor在执行查询操作的时候,会通过createCacheKey方法根据statementId/offset/limit/sql/parameter封装CacheKey,将以上五个参数放在一个 List 中。- 之后
BaseExecutor使用CacheKey执行query方法,该方法中调用PerpetualCache的getObject方法查询缓存中是否存在当前 key,如果存在,则取缓存中的数据结果;如果不存在,则调用queryFromDatabase方法查询数据库,最终再将查询结果放入缓存。
二、Mybatis 缓存 - 二级缓存
二级缓存的作用范围是
namespace。它支持多个SqlSeesion,并且即使是两个不同的 Mapper,但是如果它们的namespace一致,那也能共享缓存。二级缓存默认不开启,需要在全局配置中声明: ,此外,还需要在指定的 Mapper 中配置上
二级缓存还依赖对应的 pojo 实现 Serializable 接口,这是因为二级缓存的介质可以不止内存,通过扩展
Cache接口,还可以将数据缓存在磁盘中,所以需要实现序列化接口。
2.1 二级缓存图解
- 二级缓存默认是全局开启的,使用的时候只需要在具体的 Mapper 处加上
<cache/> - 当第一个
SqlSession执行 SQL 的时候,会查询二级缓存中是否有数据,此时还没有的话,会直接访问数据库查询,并将查询的结果数据序列化存储 - 此时第二个
SqlSession在同一个namespace下执行相同的 SQL 时,会在缓存中检索,如果命中了,则直接将数据取出并返回。注意:如果第一个SqlSession还没有close,那么第二次的SqlSession还是会认为当前的缓存是未命中的 - 和一级缓存一样,如果两次查询之间存在了命令类操作,那么缓存也是会被清空的
三、Mybatis 缓存 - 三级缓存
不论是一级缓存,还是二级缓存,都是内存级别的缓存,内存级缓存在分布式场景下缺少了访问公共性,每个服务实例仅在自己的内存中维护,一方面导致了数据的冗余,另一方面数据的过于分散增加了一致性维护的成本。
在分布式场景下通常使用分布式缓存策略统一数据节点缓存,所谓的三级缓存即:实现
Cache接口,整合缓存中间件,比较流行的缓存中间件目前是Redis,并且 Mybatis 也官方实现了整合 Redis 的方案。
- Mybatis 官方实现的三级缓存通过整合 Redis 中间件,实现 Cache 接口
RedisCache通过RedisConfigurationBuilder创建RedisConfig并解析,其中RedisConfig继承了JedisPoolConfig,最终能够使用 Redis 客户端Jedis创建连接池JedisPool- 三级缓存操作的过程就是通过调用回调方法
RedisCallback#doWithRedis(Jedis jedis)实现的