[Mybatis]-[基础支持层]-二级缓存支持

该系列文章针对 Mybatis 3.5.1 版本

Mybatis 中针对缓存的使用,可以分为:一级缓存和二级缓存。

一、二级缓存

1.1、介绍

二级缓存在 Mybatis 中默认是不开启。准确的来讲应该是二级缓存的全局配置开关是默认开启的但是想要二级缓存生效,还需要进行配置。

二级缓存的作用范围是同一个 namespace 下的mapper 映射文件内容。

多个 SqlSession 之间可以共享缓存内容。

在 Mybatis 中,SQL 查询的发起与执行的逻辑处理可以划分为核心处理层,在核心处理层中,所有执行操作的起始都是 Executor 执行器。

执行器实现类图如下

[Mybatis]-[基础支持层]-二级缓存支持

如图,所有 Executor 执行器的实现都继承了抽象父类 BaseExecutor ,同时 BaseExecutor是一个模板抽象类,定义了 Executor 的执行结构。

针对一级缓存,一级缓存的实现在 BaseExecutor 中,所以所有的 Executor 都支持一级缓存,且一级缓存不存在开启关闭开关,只能通过两种方式来使得一级缓存失效

  • 通过一级缓存作用域,配置 STATEMENT ,使得每次缓存存储之后,主动清除
  • 通过 Mapper.xml 中配置 flushCache=true 属性,使得每次查询前都先清除缓存,达到缓存失效的目的。

1.2、开启二级缓存

二级缓存由执行器装饰器 CachingExecutor 实现。

而 CachingExecutor 装饰器由 mybatis-config.xml 中配置 <setting name="cacheEnabled" value="true" /> 来控制,相当于开启二级缓存的开关配置。

CachingExecutor 是一个装饰器,用来给其他 Executor 提供二级缓存的功能增强。其本身不具备Executor 功能。

通过 Executor 对象实例的获取,验证 cacheEnabled=true 开关对 二级缓存功能的控制,相关代码如下(Configuration#newExecutor

[Mybatis]-[基础支持层]-二级缓存支持

如上述代码,配置了 cacheEnabled=true 相当于开启了二级缓存功能,Mybatis 会在Executor 基础实现类上进行二级缓存支持功能增强,缓存的增强通过装饰器 CachingExecutor来支持。

1.3、使用二级缓存

Mybatis 中开启了二级缓存功能,此时二级缓存并没有真正的生效,二级缓存的使用需要按需分配,在相关 mapper.xml 或者 Mapper.class 通过 xml标签或者注解的方式显示配置开启,二级缓存才会生效。

相关Mybatis 源码如下

[Mybatis]-[基础支持层]-二级缓存支持

开了二级缓存支持,执行器会被 CachingExecutor 进行增强,不过是否使用二级缓存还需要判断相关的 mapper.xml 中是否配置了 <cache> 标签或者 Mapper.class 中是否追加了 @CacheNamespace注解。

1.4、二级缓存使用配置案例

  • 基于 Mapper.xml 配置

只对 xml 中的 <select> 有效,对于使用注解 @Select 的无效。

[Mybatis]-[基础支持层]-二级缓存支持
  • 基于注解配置

基于注解配置,只对使用 @Select 注解查询的有效,针对 xml 的 <select> 查询sql 无效。

[Mybatis]-[基础支持层]-二级缓存支持

注意:mapper.xml 使用 <cache>开启二级缓存和 Mapper.java 使用 @CacheNamespace 不能共存,否则报错。

1.5、二级缓存使用逻辑

Mybatis 对二级缓存使用逻辑如下:

  • 针对查询查询得到二级缓存,直接从二级缓存中取结果并返回查询不到二级缓存,从一级缓存中取一级缓存也没有,从数据库中查询,然后更新缓存,并返回结果

相关代码如下:(CachingExecutor#query)

  • 针对,增删改,涉及到增删改的操作,直接清除缓存
    [Mybatis]-[基础支持层]-二级缓存支持

二、二级缓存线程安全问题以及事务问题

2.1、线程安全问题

二级缓存和命名构建绑定,也就是二级缓存在 SqlSession 间是共享的,此时就存在线程安全性问题。

针对线程安全性问题可以通过 SynchronizedCache 装饰类,还实现缓存的线程安全问题。

2.2、事务问题-脏读

一级缓存是 SqlSession 级别的,一级缓存也会带来脏读问题,但是基本上不会有啥问题。
正常开发人员不会在一个SqlSession里面修改完数据,在查出来,再修改一次。

二级缓存不一样,二级缓存针对 SqlSession 是共享的,所以二级缓存产生的脏读问题,会影响到数据的一致性问题。

2.2.1、脏读问题的产生

二级缓存存在于 MapperStatment 中,所有 SqlSession 共享该缓存

问题如下:

[Mybatis]-[基础支持层]-二级缓存支持

如图所示,存在两个同时开启的事务,而二级缓存对于这两个事务是共享的。事务A在未提交前,进行了 record 的更新,然后查询,将变更后的数据刷入二级缓存中,此时事务A未提交。

事务B 在事务A 更新了 record 并把更新数据刷入缓存后进行了查询,此时事务B查询到的是事务A未提交的数据,此时就造成了脏读。

2.2.3、解决脏读

针对二级缓存脏读问题,Mybatis 使用 TransactionalCache 来解决。

TransactionCache 作为中转站,只有提交的事务,才会把缓存数据刷入到二级缓存中。

TransactionalCache 同样也是缓存装饰器,用来解决事务问题,
在 Mybatis 中 TransactionalCache 统一由 TransactionalCacheManager 来管理,
关键代码如下:

[Mybatis]-[基础支持层]-二级缓存支持

TransactionalCacheManager 仅仅提供了管理功能,
管理了 TransactionalCache 和 Cache 的映射关系,真正的缓存操作在 TransactionalCache 中完成。

在引入了 TransactionCache 作为中转站后,多个事务的脏读问题得到解决,如下图:

[Mybatis]-[基础支持层]-二级缓存支持

只有在事务提交之后,才会将缓存刷入二级缓存中。
对应的源码在 TransactionCache#commit 中,代码如下:

[Mybatis]-[基础支持层]-二级缓存支持

在二级缓存数据处理过程中,相关的缓存数据暂存在 TransactionalCache#entriesToAddOnCommit 中,
当事务提交后,所有的缓存数据,才会刷入到二级缓存中。

三、二级缓存不可重复读问题

这是一个无法解决的问题。
不可重复读概念:同一个事务中读取同一个数据多次,存在读取结果不一样的问题。

启用了二级缓存,就存在一个供多个 SqlSession 共享的缓存池,缓存池中数据在事务提交后会刷新。

仍然以下图为例:

[Mybatis]-[基础支持层]-二级缓存支持

假设此时初始 record=10,针对 TransactionA 和 TransactionB 存在如下操作:

step1、TransactionB 查询 record ,此时二级缓存没有数据,查询数据库 record = 10
step2、TransactionA 更新 record = 11,同时提交事务,此时数据库和共享缓存中 record=11
step3、TransactionB 查询 record ,命中二级缓存 record = 11

二级缓存的事务隔离级别脱离了底层DB(如:Mysql)的事务隔离级别。即使底层DB支持更高级别的事务隔离,但是Mybatis 开了二级缓存就会存在不可重复读的问题。

来源:花好夜猿,本文观点不代表自营销立场,网址:https://www.zyxiao.com/p/86165

发表评论

登录后才能评论
侵权联系
返回顶部