Mybatis成神之路-Mybatis一级缓存和二级缓存简单总结

667

一 前置知识

缓存介绍

MyBatis中存在两个缓存,一级缓存和二级缓存

一级缓存: 也叫做会话级缓存,生命周期仅存在于当前会话,不可以直接关关闭。但可以通过flushCache和localCacheScope对其做相应控制,1级缓存无法跨线程使用
二级缓存(默认不开启): 应用级缓存,缓存对象存在于整个应用周期,而且可以跨线程使用,二级缓存有更高的命中率,适合缓存一些修改较少的数据。在流程上是先访问二级缓存,在访问一级缓存。
使用场景:静态表,字典表,权限表一般用来放在二级缓存中

Mybatis一级缓存和二级缓存的最终实现

分类

一级缓存的实现:
一级缓存的组成:BaseExecutor执行器负责,一级缓存,事务管理共性功能,一级缓存直接由BaseExecutor的PerpetualCache类型的localCache组成

二级缓存的实现
二级缓存的组成:二级缓存由SqlSession,CachingExecutor,TransactionalCacheManager,TransactionalCache,然后由暂存区类TransactionalCache的delegate指向责任链,责任链的尽头就是二级缓存存储类组成 SqlSession,CachingExecutor,TransactionalCacheManager,TransactionalCache生命周期都是SqlSession

共同点

1 一二级缓存的实现都是PerpetualCache类,该类中都维护了一个HashMap作为缓存存储的数据结构
2 缓存key和value的创建是相同的,一级缓存的实现就是一个HashMap,value就是一个缓存值,一般来说是List数据集,key就是根据SQL语句,分页条件查询参数,Statement的ID,Mapper综合起来创建的key

区别

1 一级缓存是生命周期存在于当前会话中,不能跨会话,只能被一个线程持有

2 二级缓存生命周期存在于当前应用中,可以跨线程使用,二级缓存有更高的命中率,适合缓存一些修改较少的数据。在流程上是先访问二级缓存,在访问一级缓存

3 一级缓存的更新是实时的,从数据库中查完数据就立刻更新缓存

4 二级缓存的更新需要使用当前会话commit操作才能更新二级缓存

针对一个简单查询的一二级缓存代码分析

@Test
public void cacheTest6() {
    SqlSession session1 = factory.openSession(true);
    session1.getMapper(UserMapper.class).selectByid(1);
    session1.getMapper(BlogMapper.class).selectById(1);
    System.out.println(session1);
}

在System.out.println断点debug

1 一级缓存和事务管理等具体实现存在于BaseExecutor中,该执行器用于处理三种执行器的共性问题,CachingExecutor二级缓存执行器的delegate属性指向BaseExecutor

2 CachingExecutor的delegate属性指向执行器,tcm是事务缓存管理器,其中有多个暂存区(value),每个暂存区间接指向责任链的尽头的存储二级缓存的PerpetualCache类

3 每个暂存区指向唯一一个责任链最终的二级缓存,而每个二级缓存可以被多个暂存区指向

Mybatis一级缓存和二级缓存的设计目的

一级缓存的设计目的

目的1:为了和Spring的声明式事务进行一个集成,没有事务的情况下,Mybatis对于Spring来说就是一个执行JDBC的数据源
事务指的是:多次SQL的增删改查的请求用的是同一个连接,这个连接不是自动提交的,等执行完若干个增删改查SQL后,一起提交,连接的打开,和连接的提交,中间执行的SQL语句就被称为事务。

目的2:如果一个一级缓存没有的话,甚至会导致Mybatis不会去执行嵌套查询

目的3:在同一个会话期间内,多次去执行相同的SQL语句的查询也是非常普遍和正常的,这个是完全没必要去查询数据库的,二级缓存可以没有,一级缓存一定要有

目的4:在某些情况下1级缓存和嵌套查询的循环依赖问题有关

二级缓存设计目的

目的1:减少后端服务器请求IO次数,缓存对象存在于整个应用周期,二级缓存有更高的命中率,适合缓存一些修改较少的数据。在流程上是先访问二级缓存,在访问一级缓存。

目的2:有时候查询的时候,特别是循环查询的时候,会比较慢,这样时候使用二级缓存就非常的好了

缺点:
1 全部开启二级缓存是有风险和可能造成一些数据不一致性的问题
2 二级缓存,缓存数据对象使用的是jdk序列化,反序列化,效率较低
3 只能适合于单机使用,建议使用分布式缓存Redis

Mybatis一级缓存和二级缓存的特性

一级缓存特性
1 若一级缓存命中,则得到的数据集是同一个,即两个引用指向同一个数据集
2 若使用的执行器是ReuseExecutor,在同一个会话中调用的SQL相同,即使参数不同,StatementID不同,Mapper不同,也会重用statement 二级缓存特性
1 若二级缓存命中,得到的数据集不是同一个,因为二级缓存的存储过程中经过责任链的SerializedCache会将存储的数据,和拿出的数据进过jdk序列化和反序列化,所以得到的数据对象都不是同一个,默认从缓存中取出的key对应的对象不是同一个,由于SerializedCache会将putObject的数据进行序列化,getObject数据进行反序列化,所以同时get同一个key,得到的对象不是同一个

@Test
    public void cacheTest4() {
        Cache cache = configuration.getCache("org.coderead.mybatis.UserMapper");
        User user = Mock.newUser();
        cache.putObject("luban", user);// 设置缓存
        // 线程1
        Object luban = cache.getObject("luban");
        // 线程2
        Object luban1 = cache.getObject("luban");
        System.out.println(luban==luban1);
    }

2 二级缓存必须在会话提交后才会真正填充
基于这个特性设计二级缓存:
二级缓存不是实时更新的,需要一个地方暂时存储变更的数据-即暂存区,在二级缓存结构设计当中,我们的每个会话它都会有若干个暂存区,这些暂存区全部都统一由一个叫做事务会话管理器来统一进行管理

为什么提交以后才能命中二级缓存?标准答案(为了保证数据一至性,二级缓存必须是会话提交之才会真正填充(回滚后不生效)),因为要保证提交后才能命中二级缓存,所以二级缓存结构设计复杂度提升了很多

如上图两个会话在修改同一数据,当会话二修改后,在将其查询出来,假如它实时填充到二级缓存,而会话一就能过缓存获取修改之后的数据,但实质是修改的数据回滚了,并没真正的提交到数据库。

所以为了保证数据一至性,二级缓存必须是会话提交之才会真正填充(回滚后不生效),包括对缓存的清空,也必须是会话正常提交之后才生效。

3 会话,暂存区,事务管理器(生命周期是会话)都是一体的,只要会话结束,这些都会销毁掉,但是二级缓存的缓存区(Mapper命名空间)会一直存在,应用级别的生命周期,只要程序运行,就一直存在。

4 责任链各个节点分析

5 如果在Mapper中执行了update操作(任意命名空间的update,delete,insert操作都会让二级缓存失效) 会清空当前Mapper缓存空间中的所有数据,设置此注解则不会清空

二级缓存配置策略

介绍:
二级默认缓存默认是不开启的,需要为其声明缓存空间才可以使用,通过@CacheNamespace 或 为指定的MappedStatement声明。声明之后该缓存为该Mapper所独有,其它Mapper不能访问。如需要多个Mapper共享一个缓存空间可通过@CacheNamespaceRef 或进行引用同一个缓存空间。@CacheNamespace 详细配置见下表:

@CacheNamespace注解

配置说明:

1 implementation 指定缓存的存储实现类,默认是用HashMap存储在内存当中,设置缓存最终实现
2 eviction 指定缓存溢出淘汰实现类,默认LRU ,清除最少使用,设置溢出淘汰策略
3 flushInterval 设置缓存定时全部清空时间,默认不清空(清空整个缓存),缓存有效期,默认是0表示永久有效
4 size 指定缓存容量,超出后就会按eviction指定算法进行淘汰,缓存的容量
5 readWrite true即通过序列化复制,来保证缓存对象是可读写的,默认true,需不需要进行序列化
6 blocking 为每个Key的访问添加阻塞锁,防止缓存击穿,防止缓存穿透
7 properties 为上述组件,配置额外参数,key对应组件中的字段名,给缓存添加相关属性\

声明的缓存空间并不是说当前应用的所有SQL请求都走这个缓存空间,每个缓存空间都会指定一个MappedStatement,只有当前MappedStatement下面的CRUD操作才可以使用这个二级缓存

@Options(针对于Mapper接口的方法)