Mybatis
一、下载
从gitee下载mybatis源码
官网: 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会话来解决。
总结:
- MyBatis的一级缓存是SqlSession级别的,但是它并不定义在SqlSessio接口的实现类DefaultSqlSession中,而是定义在DefaultSqlSession的成员变量Executor中,Executor是在openSession的时候被实例化出来的,它的默认实现为SimpleExecutor.
- MyBatis中的一级缓存,与有没有配置无关,只要SqlSession存在,MyBastis一级缓存就存在,localCache的类型是PerpetualCache,它其实很简单,一个id属性+一个HashMap属性而已,id是一个名为"localCache"的字符串,HashMap用于存储数据,Key为CacheKey,Value为查询结果
- 一级缓存样例
\
三、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存储【核心功能】
即缓存数据库存储在哪里?常用的方案如下:
- 内存:最简单就是在内存当中,不仅实现简单,而且速度快。内存弊端就是不能持久化,且容易有限。
- 硬盘:可以持久化,容量大。但访问速度不如内存,一般会结合内存一起使用。
- 第三方集成:在分布式情况,如果想和其它节点共享缓存,只能第三方软件进行集成。比如Redis.
3.2.1 溢出淘汰【核心功能】
无论哪种存储都必须有一个容易,当容量满的时候就要进行清除,清除的算法即溢出淘汰机制。常见算法如下:
- FIFO:先进先出
- LRU:最近最少使用
- WeakReference: 弱引用,将缓存对象进行弱引用包装,当Java进行gc的时候,不论当前的内存空间是否足够,这个对象都会被回收
- SoftReference:软件引用,基机与弱引用类似,不同在于只有当空间不足时GC才才回收软引用对象。
3.2.2 其它功能
- 过期清理:指清理存放数据过久的数据
- 线程安全:保证缓存可以被多个线程同时使用
- 写安全:当拿到缓存数据后,可对其进行修改,而不影响原本的缓存数据。通常采取做法是对缓存对象进行深拷贝。
3.3 二级缓存责任链设计
这么多的功能,如何才能简单的实现,并保证它的灵活性与扩展性呢?这里MyBatis抽像出Cache接口,其只定义了缓存中最基本的功能方法:
- 设置缓存
- 获取缓存
- 清除缓存
- 获取缓存数量
然后上述中每一个功能都会对应一个组件类,并基于装饰者加责任链的模式,将各个组件进行串联。在执行缓存的基本功能时,其它的缓存逻辑会沿着这个责任链依次往下传递。
\
这样设计有以下优点:
- 职责单一:各个节点只负责自己的逻辑,不需要关心其它节点。
- 扩展性强:可根据需要扩展节点、删除节点,还可以调换顺序保证灵活性。
- 松耦合:各节点之间不没强制依赖其它节点。而是通过顶层的Cache接口进行间接依赖。
3.4 设计模式
3.4.1 装饰器模式
3.4.2 责任链模式
四、整体调用链路
点击链接加入BoardMix中的文件「mybatis缓存流程」,boardmix.cn/app/share?t…
责任链具体实现
点击链接加入BoardMix中的文件「mybatis责任链具体实现」,boardmix.cn/app/share?t…