Mybatis一级缓存与二级缓存
创始人
2024-05-28 17:51:37
0

一、MyBatis 缓存

缓存就是内存中的数据,常常来自对数据库查询结果的保存。使用缓存,我们可以避免频繁与数据库进行交互,从而提高响应速度。

MyBatis 也提供了对缓存的支持,分为一级缓存和二级缓存,来看下下面这张图:

一级缓存是 SqlSession 级别的缓存。在操作数据库时需要构造 SqlSession 对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的是 SqlSession 之间的缓存数据区(HashMap)是互相不影响。

二级缓存是 Mapper 级别的缓存,多个 SqlSession 去操作同一个 Mapper 的 sql 语句,多个 SqlSession 可以共用二级缓存,二级缓存是跨 SqlSession 的。

相信大家看完这张图和解释心里应该有个底了吧,这对后面分析 MyBatis 的一级、二级缓存机制很有帮助,那话不多说,我们直接进入主题了。

缓存是在哪起作用的?

个人认为mybatis一级缓存和二级缓存并不是一个很好的设计,工作中我基本上也不会使用一级缓存和二级缓存,因为一旦使用不当会造成很多问题,所以我们今天就来看看到底会有什么问题?

Executor的设计是一个典型的装饰者模式,SimpleExecutor,ReuseExecutor是具体实现类,而CachingExecutor是装饰器类。

可以看到具体组件实现类有一个父类BaseExecutor,而这个父类是一个模板模式的典型应用,操作一级缓存的操作都在这个类中,而具体的操作数据库的功能则让子类去实现。

「二级缓存则是一个装饰器类,当开启二级缓存的时候,会使用CachingExecutor对具体实现类进行装饰,所以查询的时候一定是先查询二级缓存再查询一级缓存」

「那么一级缓存和二级缓存有什么区别呢?」

一级缓存

// BaseExecutor
protected PerpetualCache localCache;

一级缓存是BaseExecutor中的一个成员变量localCache(对HashMap的一个简单封装),因此一级缓存的生命周期与SqlSession相同,如果你对SqlSession不熟悉,你可以把它类比为JDBC编程中的Connection,即数据库的一次会话。

「一级缓存和二级缓存key的构建规则是一致的,都是一个CacheKey对象,因为Mybatis中涉及动态SQL等多方面的因素,缓存的key不能仅仅通过String来表示」

当执行sql的如下4个条件都相等时,CacheKey才会相等

  1. mappedStatment的id

  1. 指定查询结构集的范围

  1. 查询所使用SQL语句

  1. 用户传递给SQL语句的实际参数值

「当查询的时候先从缓存中查询,如果查询不到的话再从数据库中查询」

当使用同一个SqlSession执行更新操作时,会先清空一级缓存。因此一级缓存中内容被使用的概率也很低

一级缓存测试

首先是创建示例表student,创建对应的POJO类和增改的方法,具体可以在entity包和mapper包中查看。

CREATE TABLE `student` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(200) COLLATE utf8_bin DEFAULT NULL,`age` tinyint(3) unsigned DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin;

在以下实验中,id为1的学生名称是凯伦

「实验1」

我们可以看到,只有第一次真正查询了数据库,后续的查询使用了一级缓存。

「实验2」

我们可以看到,在修改操作后执行的相同查询,查询了数据库,一级缓存失效。

「实验3」

「MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement,即进行如下配置」

当mybatis和spring整合后

  1. 在未开启事务的情况之下,每次查询,spring都会关闭旧的sqlSession而创建新的sqlSession,因此此时的一级缓存是没有起作用的

  1. 在开启事务的情况之下,spring使用threadLocal获取当前线程绑定的同一个sqlSession,因此此时一级缓存是有效的,当事务执行完毕,会关闭sqlSession

「当mybatis和spring整合后,未开启事务的情况下,不会有任何问题,因为一级缓存没有生效。当开启事务的情况下,可能会有问题,由于一级缓存的存在,在事务内的查询隔离级别是可重复读,即使你数据库的隔离级别设置的是提交读」

二级缓存

// Configuration
protected final Map caches = new StrictMap<>("Caches collection");

「而二级缓存是Configuration对象的成员变量,因此二级缓存的生命周期是整个应用级别的。并且是基于namespace构建的,一个namesapce构建一个缓存」

「二级缓存不像一级缓存那样查询完直接放入一级缓存,而是要等事务提交时才会将查询出来的数据放到二级缓存中。」

因为如果事务1查出来直接放到二级缓存,此时事务2从二级缓存中拿到了事务1缓存的数据,但是事务1回滚了,此时事务2不就发生了脏读了吗?

「二级缓存的相关配置有如下3个」

「1.mybatis-config.xml」


这个是二级缓存的总开关,只有当该配置项设置为true时,后面两项的配置才会有效果

从Configuration类的newExecutor方法可以看到,当cacheEnabled为true,就用缓存装饰器装饰一下具体组件实现类,从而让二级缓存生效

「2.mapper映射文件中」mapper映射文件中如果配置了中的任意一个标签,则表示开启了二级缓存功能,没有的话表示不开启

二级缓存的部分配置如上,type就是填写一个全类名,用来指定二级缓存的实现类,这个实现类需要实现Cache接口,默认是PerpetualCache(你可以利用这个属性将mybatis二级缓存和Redis,Memcached等缓存组件整合在一起) org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache

这个eviction表示缓存清空策略,可填选项如下

典型的装饰者模式的实现,换缓存清空策略就是换装饰器。

「3.