接上次聊到了mybatis数据源组件后,今天我们来看看大家平时可能都用了的缓存组件。其实缓存组件在我们项目中要谨慎地选择使用,用不好不仅不会带来性能上的提升,反而会出现数据的错乱问题,为什么呢?那莫过于从mybatis缓存组件源码中来找到答案了。
整体设计架构
缓存的概况
1.首先mybatis的缓存分为一级缓存和二级缓存,一级缓存默认是开启的(你可以再mapper.xml中select标签加入flushCache="true"将它关闭);一级缓存的生命周期是SqlSession级别,它是线程安全的方式;同一个会话查询时,mybatis会把执行的方法和参数通过算法生成key, 将键值和查询结果存入map对象中。如果查询的方法和参数完全一致,那么算法会生成相同key, 这时若缓存中存在则直接返回缓存中的结果。
2.二级缓存需要开启的点有两个地方:第一个地方: 在mybatis-config.xml文件中的setting标签中设置, cacheEnabled="true"(mybatis全局开关,默认开启); 第二地方:在需要开启缓存的mapper接口的xml文件中加入
映射语句文件中的所有 select 语句将会被缓存。
eviction: 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。
flushInterval: 根据时间表(比如 no Flush Interval,没有刷新间隔), 缓存不会以任何时间顺序 来刷新。
size: 缓存会存储列表集合或对象(无论查询方法返回什么)的 512个引用。
readOnly: 缓存会被视为是 read/write(可读/可写)的缓存;
3.不管是一级缓存还是二级缓存,当有INSERT,UPDATE,DELETE语句执行时,都会自动刷新相关缓存;
4.二级缓存的生命周期为应用程序级,二级缓存的缓存单位时namespace,不同namespace之间允许共享同一缓存;
我们今天分析mybatis缓存组件要解决以下三个问题:
1.二级缓存的整体架构、关系以及设计思路;
2. 这种设计的好处 ?
3. 分析一些比较有代表性的部件源码;
4. 缓存能力在整个mybatis中的集成;
先来看看mybatis缓存组件的核心设计结构
核心设计结构
以上设计结构中:
PerpetualCache: 为缓存提供默认实现(基础的二级缓存能力) ;
其它均为Cache的各种装饰器
SynchronizedCache: 同步装饰器,赋予Cache同步、线程安全的二级缓存的能力;
SerializedCache:序列化装饰器,赋予Cache数据二进制流序列化能力;
LoggingCache:缓存日志装饰器,赋予Cache日志打印能力;
LruCache:清空缓存装饰器,赋予Cache清理淘汰策略(默认LRU, 最近最少使用清空策略);
以上四个装饰器是mybatis二级缓存默认自带的各种能力配置。我们通过修改
装饰器设计模式
装饰器设计模式作用是:允许动态向现有对象添加新功能;是一种代替继承的技术。当前对象无需通过继承扩展父类的功能,相比于继承它更加灵活且避免了子类的快速增加。
Component: 组件作为装饰器的核心接口,它定义了装饰器需要实现的行为;
ConcreteComponent:组件实现类,实现了Component所需的行为的基本实现,后面各个具体的装饰器都基于该对象进行扩展;
Decorator: 装饰器的基类(需要时可附加此类),抽象多个装饰器在实现过程装的一些公共行为和基础特性;
ConcreteDecorator:具体的装饰器类,它继承于装饰器基类,实现某一个特性的装饰器行为;
装饰器设计模式的优点
类的继承和代理,从本质上讲只能算一种静态的设计。在设计时要具体地知道对象或类需要扩展哪个特性,较少的类没问题,但当
1.基础类需要扩展的特性很多;
2.扩展的这些特性设计人员并清楚怎样装配,而需要开放给使用它的用户个性化的组合;
遇到以上两个需求的时候,继承和代理就显得力不从心,要么需要增加繁杂的子类;要么需要修改原有增强功能的业务代码,这些都违背了单一职责和开闭原则。
所以mybatis在二级缓存的增强特性需要用户的个性化配置、添加和切换的设计目标下,选择了装饰器模式。
OK这里都明白后,我们接下来分析一下BlockingCache阻塞式缓存特性。
BlockingCache阻塞式缓存
直接上核心的实现源码,我们来看看它的实现细节
从以上源码我们不难发现:此装饰器在提供线程安全的缓存读写的同时,又考虑到了拿锁的开销。所以采用了粒度较小的分片锁机制,只针对特定的缓存key加锁。这样做至少有两个好处:
1.提高了缓存读写的效率,减小了阻塞的粒度;
2.在高并发时,这种对key加锁的机制可以有效解决缓存击穿的问题;
OK,这里get到了后,下面我们就来继续分享一下mybatis缓存的小点CacheKe类
缓存键CacheKey
由上截图,缓存的接口API我们可以得知,mybatis存取缓存的key是一个Object类型,这个Object实际传入的参数就是CacheKey对象
那Cachekey对象中会添加设置那些参数呢?mybatis缓存的key跟以下因素有关:
1.存储在mappedStatement对象中的id = namespace+id(命令id);
2.查询使用的sql语句;
3.查询传递的sql实际参数值;
4.指定查询结果集的范围;
口说无凭,我们看看源码(位置:BaseExecutor-->createCacheKey()方法)
OK如果都get到了以上的点,那我们来看看缓存技术在mybatis中如何被集成进去的
缓存的集成
先看看MapperBuilderAssistant-->useNewCache()方法
上图CacheBuilder构建Cache对象时,采用了链式的编程风格。我们来看它源码实现
很明显,这里采用的是建造者设计模式对CacheBuilder的各个部分进行的分步构建,最后调用build构造出Cache对象。此模式在mybatis源码中使用也比较多。
Builder: 抽象建造者接口,是定义建造的行为的基础接口;
ConcreteBuilder: 具体建造者实现类,抽象建造者行为的实现类;
Product: 建造者模式,最终生产出的产品;
Director: 使用建造者的场景类或入口;
建造者模式的本质:对内将一个复杂对象的创建过程解耦;对外屏蔽对象创建细节的复杂度。
适合场景:对象本身比较复杂或者它的创建有多个步骤(部分)组成,经过一系列分步构建,最终组合成完整的对象的场景。
类比Cache组件,各个角色为
它这里并没有定义基础的建造者接口。
一级缓存调用入口: BaseExecutor-->query()方法
二级缓存调用入口: CachingExecutor---->query()方法
以上截图代码已经把答案说得很详细了,相信不用我再多说。
总结
以上就是mybatis缓存组件设计和集成的整个过程。更多细节的东西,大家如果有兴趣可以多读一下mybatis源码。通过分析源码你会得到更多的收获!如果有其它问题欢迎在文章下方留言,请继续关注!