接下来通过实验,了解一级缓存的效果,每个单元测试后都请恢复被修改的数据。

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

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」

开启一级缓存mybatis一级缓存和二级缓存,范围为会话级别,调用三次,代码如下所示:

mybatis一级缓存和二级缓存_缓存级别_缓存级数

执行结果:

缓存级数_mybatis一级缓存和二级缓存_缓存级别

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

「实验2」

增加了对数据库的修改操作,验证在一次数据库会话中,如果对数据库发生了修改操作,一级缓存是否会失效

缓存级别_mybatis一级缓存和二级缓存_缓存级数

执行结果:

缓存级别_mybatis一级缓存和二级缓存_缓存级数

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

「实验3」

开启两个,在中查询数据,使一级缓存生效,在中更新数据库,验证一级缓存只在数据库会话内部共享。

mybatis一级缓存和二级缓存_缓存级别_缓存级数

输出如下

缓存级别_缓存级数_mybatis一级缓存和二级缓存

和读的时相同的数据,但是都查询了数据库,说明了「一级缓存只在数据库会话层面共享」

更新了id为1的学生的姓名,从凯伦改为了小岑,但之后的查询中,id为1的学生的名字还是凯伦,出现了脏数据,也证明了之前的设想,一级缓存只在数据库会话层面共享

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


原因也很简单,看的query()方法,当配置成时,每次查询完都会清空缓存

缓存级数_mybatis一级缓存和二级缓存_缓存级别

「看到这你可能会想,我用后没设置这个参数啊,好像也没发生脏读的问题啊,其实是因为你和整合了」

当和整合后(整合的相关知识后面还有一节)

在未开启事务的情况之下,每次查询,都会关闭旧的而创建新的,因此此时的一级缓存是没有起作用的在开启事务的情况之下,使用获取当前线程绑定的同一个,因此此时一级缓存是有效的,当事务执行完毕,会关闭

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

二级缓存

mybatis一级缓存和二级缓存_缓存级数_缓存级别

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

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

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

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

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

「1.-.xml」


 

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

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

缓存级数_缓存级别_mybatis一级缓存和二级缓存

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


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

org....nt#

缓存级数_mybatis一级缓存和二级缓存_缓存级别

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

选项解释装饰器类LRU最近最少使用的:移除最长时间不被使用的对象先进先出:按对象进入缓存的顺序来移除它们软引用:移除基于垃圾回收器状态和软引用规则的对象弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象

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

缓存级别_缓存级数_mybatis一级缓存和二级缓存

「3.节点中的属性」

该属性表示查询产生的结果是否要保存的二级缓存中,属性的默认值为true,这个配置可以将二级缓存细分到语句级别

测试二级缓存

二级缓存是基于实现的,即一个映射文件用一个缓存

在本实验中,id为1的学生名称初始化为点点。

「实验1」

测试二级缓存效果,不提交事务,查询完数据后,相同的查询是否会从缓存中获取数据。

mybatis一级缓存和二级缓存_缓存级别_缓存级数

执行结果:

缓存级别_mybatis一级缓存和二级缓存_缓存级数

我们可以看到,当没有调用()方法时,二级缓存并没有起到作用。

「实验2」

测试二级缓存效果mybatis一级缓存和二级缓存,当提交事务时,查询完数据后,相同的查询是否会从缓存中获取数据。

mybatis一级缓存和二级缓存_缓存级别_缓存级数

从图上可知,的查询,使用了缓存,缓存的命中率是0.5。

「实验3」

测试操作是否会刷新该下的二级缓存。

缓存级别_mybatis一级缓存和二级缓存_缓存级数

我们可以看到,在更新数据库,并提交事务后,的 下的查询走了数据库,没有走Cache。

「实验4」

验证的二级缓存不适应用于映射文件中存在多表查询的情况。

缓存级数_mybatis一级缓存和二级缓存_缓存级别

的定义如下

缓存级别_缓存级数_mybatis一级缓存和二级缓存

通常我们会为每个单表创建单独的映射文件,由于的二级缓存是基于的,多表查询语句所在的无法感应到其他中的语句对多表查询中涉及的表进行的修改,引发脏数据问题。

执行结果:

缓存级别_缓存级数_mybatis一级缓存和二级缓存

在这个实验中,我们引入了两张新的表,一张class,一张。class中保存了班级的id和班级名,中保存了班级id和学生id。我们在中增加了一个查询方法,用于查询学生所在的班级,涉及到多表查询。在中添加了,根据班级id更新班级名的操作。

当的查询数据后,二级缓存生效。保存在的下的cache中。当的的方法对class表进行更新时,不属于的,所以下的cache没有感应到变化,没有刷新缓存。当中同样的查询再次发起时,从缓存中读取了脏数据。

「实验5」

为了解决实验4的问题呢,可以使用Cache ref,让引用命名空间,这样两个映射文件对应的SQL操作都使用的是同一块缓存了。

文件中的配置如下


执行结果:

缓存级别_mybatis一级缓存和二级缓存_缓存级数

不过这样做的后果是,缓存的粒度变粗了,多个 下的所有操作都会对缓存使用造成影响。

总结

的一级缓存和二级缓存都是基于本地的,分布式环境下必然会出现脏读。

二级缓存可以通过实现Cache接口,来集中管理缓存,避免脏读,但是有一定的开发成本,并且在多表查询时,使用不当极有可能会出现脏数据

「除非对性能要求特别高,否则一级缓存和二级缓存都不建议使用」

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注