接上次聊到了mybatis数据源组件后,今天咱们来看看你们平时可能都用了的缓存组件。其实缓存组件在咱们项目中要谨慎地选择使用,用很差不只不会带来性能上的提高,反而会出现数据的错乱问题,为何呢?那莫过于从mybatis缓存组件源码中来找到答案了。算法
总体设计架构
sql
缓存的概况编程
1.首先mybatis的缓存分为一级缓存和二级缓存,一级缓存默认是开启的(你能够再mapper.xml中select标签加入flushCache="true"将它关闭);一级缓存的生命周期是SqlSession级别,它是线程安全的方式;同一个会话查询时,mybatis会把执行的方法和参数经过算法生成key, 将键值和查询结果存入map对象中。若是查询的方法和参数彻底一致,那么算法会生成相同key, 这时若缓存中存在则直接返回缓存中的结果。设计模式
2.二级缓存须要开启的点有两个地方:第一个地方: 在mybatis-config.xml文件中的setting标签中设置, cacheEnabled="true"(mybatis全局开关,默认开启); 第二地方:在须要开启缓存的mapper接口的xml文件中加入<cache></cache>或<cache-ref/>标签,固然里面有一些属性你能够经过它们扩展cache的一些能力;缓存
映射语句文件中的全部 select 语句将会被缓存。安全
eviction: 缓存会使用 Least Recently Used(LRU,最近最少使用的)算法来收回。mybatis
flushInterval: 根据时间表(好比 no Flush Interval,没有刷新间隔), 缓存不会以任什么时候间顺序 来刷新。架构
size: 缓存会存储列表集合或对象(不管查询方法返回什么)的 512个引用。并发
readOnly: 缓存会被视为是 read/write(可读/可写)的缓存;app
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二级缓存默认自带的各类能力配置。咱们经过修改<cache/>标签的各个属性,可修改定义咱们的缓存须要具有的能力和特性。这里咱们重点来分析一个很是有意思的缓存装饰器:BlockingCache 阻塞式装饰器,在分析这个装饰器前,先来讲说组件涉及到的装饰器设计模式。
装饰器设计模式
装饰器设计模式做用是:容许动态向现有对象添加新功能;是一种代替继承的技术。当前对象无需经过继承扩展父类的功能,相比于继承它更加灵活且避免了子类的快速增长。
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源码。经过分析源码你会获得更多的收获!若是有其它问题欢迎在文章下方留言,请继续关注!