myabtis源码分析四-缓存模块分析,装饰模式的使用(代码片段)

Dark_King_ Dark_King_     2023-01-15     484

关键词:

一、Mybatis缓存模块分析

mybatis缓存模块具备以下特点:

  1. MyBatis 缓存的实现是基于 Map 的,从缓存里面读写数据是缓存模块的核心基础功能;
  2. 除核心功能之外,有很多额外的附加功能,如:防止缓存击穿,添加缓存清空策略(fifo、 lru)、序列化功能、日志能力、定时清空能力等;
  3. 附加功能可以以任意的组合附加到核心基础功能之上;

那么我们应该如何优雅的为核心功能添加附加能力呢?

有些同学可能了解使用继承的办法扩展附加功能,继承的方式是静态的,用户不能控制增加行为的方式和时机。另外,新功能的存在多种组合,使用继承可能导致大量子类存在;

基于 Map 核心缓存能力,将阻塞、清空策略、序列化、日志等等能力以任意组合的方式优 雅的增强是 Mybatis 缓存模块实现最大的难题,用动态代理或者继承的方式扩展多种附加能力的传统方式存在以下问题:这些方式是静态的,用户不能控制增加行为的方式和时机;另 外,新功能的存在多种组合,使用继承可能导致大量子类存在。综上,MyBtis 缓存模块采用 了装饰器模式实现了缓存模块; 

二、装饰器模式 

1、装饰器模式介绍

装饰器模式是一种用于代替继承的技术,无需通过继承增加子类就能扩展对象的新功能。使 用对象的关联关系代替继承关系,更加灵活,同时避免类型体系的快速膨胀。装饰器 UML 类图如下: 
 

 组件含义如下:

  • 组件(Component):组件接口定义了全部组件类和装饰器实现的行为; 
  • 组件实现类(ConcreteComponent):实现 Component 接口,组件实现类就是被装饰器 装饰的原始对象,新功能或者附加功能都是通过装饰器添加到该类的对象上的
  • 装饰器抽象类(Decorator):实现 Component 接口的抽象类,在其中封装了一个 Component 对象,也就是被装饰的对象;
  • 具体装饰器类(ConcreteDecorator):该实现类要向被装饰的对象添加某些功能; 

 我们很多人都玩过游戏,以DNF里的职业剑魂为例,装饰器模式图示如下: 

2、装饰器模式优点

装饰器相对于继承,装饰器模式灵活性更强,扩展性更强:  

  • 灵活性:装饰器模式将功能切分成一个个独立的装饰器,在运行期可以根据需要动态的 添加功能,甚至对添加的新功能进行自由的组合;
  • 扩展性:当有新功能要添加的时候,只需要添加新的装饰器实现类,然后通过组合方式 添加这个新装饰器,无需修改已有代码,符合开闭原则;

3、装饰器模式使用举例

  1. IO 中输入流和输出流的设计 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream("c://a.txt"))); 
  2. 对网络爬虫的自定义增强,可增强的功能包括:多线程能力、缓存、自动生成报表、黑 白名单、random 触发等 

三、装饰器在缓存模块的使用 

MyBatis 缓存模块是一个经典的使用装饰器实现的模块,类图如下:

  • Cache:Cache 接口是缓存模块的核 心接口,定义了缓存的基本操作; 
  • PerpetualCache:在缓存模块中扮演 ConcreteComponent 角色,使用 HashMap 来实现 cache 的相关操作; 
  • BlockingCache:阻塞版本的缓存装 饰器,保证只有一个线程到数据库去查 找指定的 key 对应的数据;BlockingCache 是阻塞版本的缓存装饰器,这个装饰器通过 ConcurrentHashMap 对锁的粒度 进行了控制,提高加锁后系统代码运行的效率(注:缓存雪崩的问题可以使用细粒度锁的方 式提升锁性能) 

源码分析:


/**
 * Simple blocking decorator 
 * 
 * Simple and inefficient version of EhCache's BlockingCache decorator.
 * It sets a lock over a cache key when the element is not found in cache.
 * This way, other threads will wait until this element is filled instead of hitting the database.
 * 
 * 阻塞版本的缓存装饰器,保证只有一个线程到数据库去查找指定的key对应的数据
 *
 *
 */
public class BlockingCache implements Cache 

  //阻塞的超时时长
  private long timeout;
  //被装饰的底层对象,一般是PerpetualCache
  private final Cache delegate;
  //锁对象集,粒度到key值
  private final ConcurrentHashMap<Object, ReentrantLock> locks;

  public BlockingCache(Cache delegate) 
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  

  @Override
  public String getId() 
    return delegate.getId();
  

  @Override
  public int getSize() 
    return delegate.getSize();
  

  @Override
  public void putObject(Object key, Object value) 
    try 
      delegate.putObject(key, value);
     finally 
      releaseLock(key);
    
  

  @Override
  public Object getObject(Object key) 
    acquireLock(key);//根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
    Object value = delegate.getObject(key);
    if (value != null) //获取数据成功的,要释放锁
      releaseLock(key);
            
    return value;
  

  @Override
  public Object removeObject(Object key) 
    // despite of its name, this method is called only to release locks
    releaseLock(key);
    return null;
  

  @Override
  public void clear() 
    delegate.clear();
  

  @Override
  public ReadWriteLock getReadWriteLock() 
    return null;
  
  
  private ReentrantLock getLockForKey(Object key) 
    ReentrantLock lock = new ReentrantLock();//创建锁
    ReentrantLock previous = locks.putIfAbsent(key, lock);//把新锁添加到locks集合中,如果添加成功使用新锁,如果添加失败则使用locks集合中的锁
    return previous == null ? lock : previous;
  
  
//根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
  private void acquireLock(Object key) 
	//获得锁对象
    Lock lock = getLockForKey(key);
    if (timeout > 0) //使用带超时时间的锁
      try 
        boolean acquired = lock.tryLock(timeout, TimeUnit.MILLISECONDS);
        if (!acquired) //如果超时抛出异常
          throw new CacheException("Couldn't get a lock in " + timeout + " for the key " +  key + " at the cache " + delegate.getId());  
        
       catch (InterruptedException e) 
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      
     else //使用不带超时时间的锁
      lock.lock();
    
  
  
  private void releaseLock(Object key) 
    ReentrantLock lock = locks.get(key);
    if (lock.isHeldByCurrentThread()) 
      lock.unlock();
    
  

  public long getTimeout() 
    return timeout;
  

  public void setTimeout(long timeout) 
    this.timeout = timeout;
    

除了 BlockingCache 之外,缓存模块还有其他的装饰器如: 

  1. LoggingCache:日志能力的缓存; 
  2. ScheduledCache:定时清空的缓存; 
  3. BlockingCache:阻塞式缓存; 
  4. SerializedCache:序列化能力的缓存; 
  5. SynchronizedCache:进行同步控制的缓存; 

 
那么问题来了,我们知道HashMap是线程不安全的,那么Mybatis 的缓存功能使用 HashMap 实现会不会出现并发安全的问题呢? 

MyBatis 的缓存分为一级缓存、二级缓存。二级缓存是多个会话共享的缓存,确实会出 现并发安全的问题,因此 MyBatis 在初始化二级缓存时,会给二级缓存默认加上 SynchronizedCache 装饰器的增强,在对共享数据 HashMap 操作时进行同步控制,所以二级 缓存不会出现并发安全问题;而一级缓存是会话独享的,不会出现多个线程同时操作缓存数 据的场景,因此一级缓存也不会出现并发安全的问题; 

四、缓存的唯一标识 CacheKey 

MyBatis 中涉及到动态 SQL 的原因,缓存项的 key 不能仅仅通过一个 String 来表示,所以通 过 CacheKey 来封装缓存的 Key 值,CacheKey 可以封装多个影响缓存项的因素;
判断两个 CacheKey是否相同关键是比较两个对象的hash值是否一致;构成CacheKey对象的要素包括:

  1. mappedStatment 的 id 
  2. 指定查询结果集的范围(分页信息) 
  3. 查询所使用的 SQL 语句 
  4. 用户传递给 SQL 语句的实际参数值

CacheKey 中 update 方法和 equals 方法是进行比较时非常重要的两个方法:

  • update 方法:用于添加构成 CacheKey 对象的要素,每添加一个元素会对 hashcode、checksum、count 以及 updateList 进行更新;
  • equals 方法:用于比较两个元素是否相等。首先比较 hashcode、checksum、count 是否 相等,如果这三个值相等,会循环比较 updateList 中每个元素的 hashCode 是否一致;

 按照这种方式判断两个对象是否相等,一方面能很严格的判断是否一致避免出现误判, 另外一方面能提高比较的效率; 

myabtis源码解析四(mybatis中statement对象的创建)

参考技术A上一期,我们分析了sql语句的创建过程,接下来,咱们进入到下一个特别重要的对象创建,statement对象的创建StatementHandler是一个非常核心接口,不仅要创建合适statement对象,再将参数放到statement对象中,执行sql,处理结果集.可以... 查看详情

jstorm与storm源码分析--basicboltexecutor与装饰模式

...asicBoltExecutor实现的。 下面我们看一下BasicBoltExecutor的源码:/***BasicBoltExecutor实现了IRichBolt接口*在该类中持有一个I 查看详情

mybatis源码分析三-数据源模块分析,工厂模式的使用(代码片段)

目录一、数据源的创建 二、简单工厂模式 1、简单工厂模式介绍2、示例代码三、工厂模式 1、工厂模式介绍2、为什么要使用工厂模式? 3、代码示例四、数据库连接池技术解析 1、数据库连接池技术介绍 2、获取资源和回... 查看详情

mybatis源码分析五mybatis的缓存

...Batis运行过程应用MyBatis缓存的二层体系一级缓存一级缓存源码分析二级缓存二级缓存源码分析二级缓存的创建缓存创建的时机二级缓存创建的方法 查看详情

mybatis源码分析五mybatis的缓存

...Batis运行过程应用MyBatis缓存的二层体系一级缓存一级缓存源码分析二级缓存二级缓存源码分析二级缓存的创建缓存创建的时机二级缓存创建的方法 查看详情

mybatis源码解析(代码片段)

...后,需要关闭会话SqlSession1.3底层1.3.1原理图:1.3.2源码结构:2、MyBatis中的缓存2.1缓存的作用2.2缓存的设计2.2.1架构设计2.2.2一级缓存和二级缓存一级缓存:session级别,默认开启开启二级缓存:2.2.3缓存的处... 查看详情

android面试宝典

...结Android性能优化ListView详解RecyclerView和ListView异同Asynctask源码分析插件化技术自定义控件事件分发机制ANR问题Art和Dalvik的区别Android关于OOM的解决方案FragmentActivity&FragmentSurfaceViewAndroid几种进程APP启动过程图片三级缓存热修复的... 查看详情

flutterdio源码分析(四)--封装

参考技术AFlutterDio源码分析(一)--Dio介绍FlutterDio源码分析(二)--HttpClient、Http、Dio对比FlutterDio源码分析(三)--深度剖析FlutterDio源码分析(四)--封装FlutterDio源码分析(一)--Dio介绍视频教程FlutterDio源码分析(二)--HttpClient、Http、Dio对比视频... 查看详情

glide4.12图片框架之多级缓存源码设计分析(代码片段)

一、Glide缓存初识在上两篇文章中,我们从源码角度分析Glide框架加载图片的流程、以及Glide图片通过巧妙的空view的Fragment的设计实现的Glide的图片加载的三大生命周期函数onStart、onStop、onDestroy。Glide的框架的源码量确实比较... 查看详情

glide4.12图片框架之多级缓存源码设计分析(代码片段)

一、Glide缓存初识在上两篇文章中,我们从源码角度分析Glide框架加载图片的流程、以及Glide图片通过巧妙的空view的Fragment的设计实现的Glide的图片加载的三大生命周期函数onStart、onStop、onDestroy。Glide的框架的源码量确实比较... 查看详情

universal-image-loader源码分析,及常用的缓存策略

讲到图片请求,主要涉及到网络请求,内存缓存,硬盘缓存等原理和4大引用的问题,概括起来主要有以下几个内容:原理示意图   主体有三个,分别是UI,缓存模块和数据源(网络)。它们之间的关系如下:① UI... 查看详情

深入分析javaweb技术内幕

一、深入web请求过程1.1B/S网络架构概述1.2如何发起一个请求1.3HTTP解析1.3.1浏览器缓存机制CTRL+F51.4DNS域名解析1.4.1DNS域名解析过程1.5  CDN工作机制1.5.1CDN动态加速二、设计模式2.1 适配器模式2.2装饰模式2.3访问者模式2.4门面设计... 查看详情

装饰模式

前几年学习设计模式的时候,看着别人的分析觉得挺有道理,也能懂 就是自己来的时候,根本无从下手,看着分析出来的代码,也不难 可是最缺乏的就是代码分析的能力,怎么根据实际的业务把代码很巧妙的拆开 并... 查看详情

beego源码分析(转)

...本身模块众多,无法简单描述所有的功能。我简单阅读了源码,记录一下beego执行过程。官方文档已经图示了beego执行 查看详情

glide4.12图片框架之多级缓存源码设计分析(代码片段)

一、Glide缓存初识在上两篇文章中,我们从源码角度分析Glide框架加载图片的流程、以及Glide图片通过巧妙的空view的Fragment的设计实现的Glide的图片加载的三大生命周期函数onStart、onStop、onDestroy。Glide的框架的源码量确实比较... 查看详情

glide4.12图片框架之多级缓存源码设计分析(代码片段)

一、Glide缓存初识在上两篇文章中,我们从源码角度分析Glide框架加载图片的流程、以及Glide图片通过巧妙的空view的Fragment的设计实现的Glide的图片加载的三大生命周期函数onStart、onStop、onDestroy。Glide的框架的源码量确实比较... 查看详情

通过源码分析mybatis的缓存

  看了通过源码分析MyBatis的缓存这篇文章后,自己跟着源码过了一遍,对mybatis的一级缓存和二级缓存有了更清楚的认识。  一级缓存是SqlSession级别的,同一个sqlSession在第二次执行一个相同参数的select语句并且第一次执行... 查看详情

mybatis源码分析之05一级缓存

...batis的一级缓存就是指SqlSession缓存,Map缓步!通过前面的源码分析知道mybatis框架默认使用的是DefaultSqlSession,它是由DefaultSqlSessionFactory创建的,下面是源码privateSqlSessionopenSessionFromDataSource(ExecutorTypeexecType,TransactionIsol 查看详情