Mybatis缓存源码解析

164 阅读6分钟

Mybatis

一、下载

从gitee下载mybatis源码

下载地址:gitee.com/mirrors/myb…

官网: mybatis.net.cn/sqlmap-xml.…

MyBatis的缓存分为一级缓存和二级缓存(全局缓存) ,缓存示意图如下图所示。默认情况下,一级缓存是开启的,不可以直接关关闭。

设置一级二级缓存禁用

mybatis:
  configuration:
    local-cache-scope: session  //statement 一级缓存
    cache-enabled: false  //二级缓存

二、Mybatis一级缓存

myBatis中存在两个缓存,一级缓存和二级缓存。

  • 一级缓存:也叫做会话级缓存,生命周期仅存在于当前会话,不可以直接关关闭。但可以通过flushCache和localCacheScope对其做相应控制。
  • 二级缓存:也叫应用级性缓存,缓存对象存在于整个应用周期,而且可以跨线程使用.

2.1 一级缓存的命中场景

关于一级缓存的命中可大致分为两个场景,满足特定命中参数,第二触发清空方法。

  • 2.1.1 缓存命中参数:

    • SQL与参数相同:
    • 同一个会话:同一个sqlSession对象
    • 相同的MapperStatement ID(样例:com.cloudwise.dosm.dao.UserMapper.queryUserById)
    • RowBounds行范围相同
  • 2.1.2 触发清空缓存

    • 手动调用clearCache
    • 执行提交回滚
    • 执行update
    • 配置flushCache=true
    • 缓存作用域为Statement

2.2一级缓存源码解析

本文所要论述的一级缓存逻辑就存在于 BaseExecutor (基础执行器)里面。当会话接收到查询请求之后,会交给执行器的Query方法,在这里会通过 Sql、参数、分页条件等参数创建一个缓存key,在基于这个key去 PerpetualCache中查找对应的缓存值,如果有主直接返回。没有就会查询数据库,然后在填充缓存。

2.2.1一级缓存的清空

缓存的清空对应BaseExecutor中的 clearLocalCache.方法。只要找到调用该方法地方,就知道哪些场景中会清空缓存了。

  • update: 执行任意增删改
  • select:查询又分为两种情况清空,一前置清空,即配置了flushCache=true。2后置清空,配置了缓存作用域为statement 查询结束合会清空缓存。
  • commit:提交前清空
  • Rolback:回滚前清空

注意:clearLocalCache 不是清空某条具体数据,而清当前会话下所有一级缓存数据。

2.3MyBatis集成Spring后一级缓存失效的问题?

很多人发现,集成一级缓存后会话失效了,以为是spring Bug ,真正原因是Spring 对SqlSession进行了封装,通过SqlSessionTemplae ,使得每次调用Sql,都会重新构建一个SqlSession,具体参见SqlSessionInterceptor。而根据前面所学,一级缓存必须是同一会话才能命中,所以在这些场景当中不能命中。

怎么解决呢?给Spring 添加事务即可。添加事务之后,SqlSessionInterceptor(会话拦截器)就会去判断两次请求是否在同一事物当中,如果是就会共用同一个SqlSession会话来解决。

总结:

  1. MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类DefaultSqlSession中,而是定义在DefaultSqlSession的成员变量Executor中,Executor是在openSession的时候被实例化出来的,它的默认实现为SimpleExecutor.
  1. MyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果
  1. 一级缓存样例

\

三、MyBatis二级缓存源码解析

二级缓存也称作是应用级缓存,与一级缓存不同的,是它的作用范围是整个应用,而且可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据。在流程上是先访问二级缓存,在访问一级缓存。

\

原理: Mapper级别,Mapper以命名空间为单位创建缓存数据结构,缓存使用的数据结构是Map,其中key是 MapperId+Offset+Limit+SQL+所有的入参。MyBatis的二级缓存是CacheExecutor实现的。CacheExecutor是Executor的代理对象。当MyBatis接收到用户的查询请求时首先会根据Map的Key在CacheExecute缓存中查询数据是否存在,存在则从缓存中取,否则将执行数据库查询操作。

\

命中二级缓存样例:

二级缓存样例: id为命名空间 value值为byte数组,序列化反序列化

开启二级缓存: 在需要使用二级缓存的Mapper配置文件中配置cache标签

-- 整体配置
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

3.1 二缓存需求

二级缓存是一个完整的缓存解决方案,那应该包含哪些功能呢?这里我们分为核心功能和非核心功能两类:

3.2存储【核心功能】

即缓存数据库存储在哪里?常用的方案如下:

  1. 内存:最简单就是在内存当中,不仅实现简单,而且速度快。内存弊端就是不能持久化,且容易有限。
  1. 硬盘:可以持久化,容量大。但访问速度不如内存,一般会结合内存一起使用。
  1. 第三方集成:在分布式情况,如果想和其它节点共享缓存,只能第三方软件进行集成。比如Redis.

3.2.1 溢出淘汰【核心功能】

无论哪种存储都必须有一个容易,当容量满的时候就要进行清除,清除的算法即溢出淘汰机制。常见算法如下:

  1. FIFO:先进先出
  1. LRU:最近最少使用
  1. WeakReference: 弱引用,将缓存对象进行弱引用包装,当Java进行gc的时候,不论当前的内存空间是否足够,这个对象都会被回收
  1. SoftReference:软件引用,基机与弱引用类似,不同在于只有当空间不足时GC才才回收软引用对象。

3.2.2 其它功能

  1. 过期清理:指清理存放数据过久的数据
  1. 线程安全:保证缓存可以被多个线程同时使用
  1. 写安全:当拿到缓存数据后,可对其进行修改,而不影响原本的缓存数据。通常采取做法是对缓存对象进行深拷贝。

3.3 二级缓存责任链设计

这么多的功能,如何才能简单的实现,并保证它的灵活性与扩展性呢?这里MyBatis抽像出Cache接口,其只定义了缓存中最基本的功能方法:

  • 设置缓存
  • 获取缓存
  • 清除缓存
  • 获取缓存数量

然后上述中每一个功能都会对应一个组件类,并基于装饰者加责任链的模式,将各个组件进行串联。在执行缓存的基本功能时,其它的缓存逻辑会沿着这个责任链依次往下传递。

\

这样设计有以下优点:

  1. 职责单一:各个节点只负责自己的逻辑,不需要关心其它节点。
  1. 扩展性强:可根据需要扩展节点、删除节点,还可以调换顺序保证灵活性。
  1. 松耦合:各节点之间不没强制依赖其它节点。而是通过顶层的Cache接口进行间接依赖。

3.4 设计模式

3.4.1 装饰器模式

3.4.2 责任链模式

四、整体调用链路

点击链接加入BoardMix中的文件「mybatis缓存流程」,boardmix.cn/app/share?t…

责任链具体实现

点击链接加入BoardMix中的文件「mybatis责任链具体实现」,boardmix.cn/app/share?t…

参考:

blog.csdn.net/Be_insighte…

blog.csdn.net/qq_37829947…

blog.csdn.net/Dongguabai/…