Mybatis源码解读(三)--缓存模块(装饰器模式)

Mybatis的缓存模块位于org.apache.ibatis.cache。

Mybatis的缓存是基于Map实现的,从缓存里读写数据是缓存模块的核心功能。Mybatis的缓存还有很多额外的附加功能,如防止缓存击穿,设置缓存清除策略,日志功能等。这些附加功能可以以任意方式的组合附加到核心功能之上,而Mybatis采取的就是装饰器模式。

装饰器模式

 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新功能,是一种用于替代继承的技术,无需通过继承增加子类就能扩展对象的新功能。相比继承灵活性更强,扩展性更强。装饰器模式将功能切分成一个个独立的装饰器,在运行期可以根据需要动态的添加功能,进行自由的组合。当有新功能需要添加时,只需要增加新的装饰器实现类,通过组合方式添加,无需修改其他的代码。

组件Component:组件接口定义了全部组件类和装饰器的行为。

组件实现类ConcreteComponent:实现了Component接口,组件实现类就是被装饰器装饰的原始对象,新功能通过装饰器添加到该类的对象上。

装饰器抽象类Decorator:实现了Component接口的抽象类,在其中封装了一个Component对象,也就是被装饰的对象。

具体装饰器类ConcreteDecorator:该实现类要向被装饰的对象添加新功能。

 

常见的装饰器

IO中的输入输出流:BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream("xxx")));

Servlet API中对request对象的装饰器

Mybatis的缓存模块

 

Mybatis缓存

Cache:Cache接口是缓存模块的核心接口,定义了缓存的基本操作。

public interface Cache {

  String getId();//缓存实现类的id

  void putObject(Object key, Object value);//往缓存中添加数据,key一般是CacheKey对象

  Object getObject(Object key);//根据指定的key从缓存获取数据

  Object removeObject(Object key);//根据指定的key从缓存删除数据

  void clear();//清空缓存

  int getSize();//获取缓存的个数
  
  ReadWriteLock getReadWriteLock();//获取读写锁

}

PerpetualCache:缓存的基础实现类,使用HashMap来实现缓存功能。

public class PerpetualCache implements Cache {

  private final String id;

  private Map<Object, Object> cache = new HashMap<>();

...

BlockingCache:阻塞版本的装饰器,保证只有一个线程到数据库去查指定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 Object getObject(Object key) {
    acquireLock(key);//根据key获得锁对象,获取锁成功加锁,获取锁失败阻塞一段时间重试
    Object value = delegate.getObject(key);
    if (value != null) {//获取数据成功的,要释放锁
      releaseLock(key);
    }        
    return value;
  }

  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();
    }
  }

...

 

其他装饰器也都类似,通过定义一个

private final Cache delegate;

复写Cache的方法,在执行delegate方法的之前,附加各自装饰器的功能。

 

CacheKey

Mybatis中通过CacheKey来封装缓存的key值。CacheKey由mapperStatment的id、分页信息、sql语句、实际参数构成。

  private final int multiplier;//参与hash计算的乘数
  private int hashcode;//CacheKey的hash值,在update函数中实时运算出来的
  private long checksum;//校验和,hash值的和
  private int count;//updateList的中元素个数
  private List<Object> updateList;
  public void update(Object object) {
	//获取object的hash值
    int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 
    //更新count、checksum以及hashcode的值
    count++;
    checksum += baseHashCode;
    baseHashCode *= count;
    hashcode = multiplier * hashcode + baseHashCode;
    //将对象添加到updateList中
    updateList.add(object);
  }
  @Override
  public boolean equals(Object object) {
    if (this == object) {//比较是不是同一个对象
      return true;
    }
    if (!(object instanceof CacheKey)) {//是否类型相同
      return false;
    }

    final CacheKey cacheKey = (CacheKey) object;

    if (hashcode != cacheKey.hashcode) {//hashcode是否相同
      return false;
    }
    if (checksum != cacheKey.checksum) {//checksum是否相同
      return false;
    }
    if (count != cacheKey.count) {//count是否相同
      return false;
    }

    //以上都不相同,才按顺序比较updateList中元素的hash值是否一致
    for (int i = 0; i < updateList.size(); i++) {
      Object thisObject = updateList.get(i);
      Object thatObject = cacheKey.updateList.get(i);
      if (!ArrayUtil.equals(thisObject, thatObject)) {
        return false;
      }
    }
    return true;
  }

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值