org.apache.ibatis.cache包源码分析

啃下MyBatis源码系列目录

啃下MyBatis源码 - 为什么要看MyBatis源码及源码结构

啃下MyBatis源码 - org.apache.ibatis.logging包源码分析

啃下MyBatis源码 - org.apache.ibatis.datasource包源码分析

啃下MyBatis源码 - org.apache.ibatis.cache包源码分析

啃下MyBatis源码 - MyBatis核心流程三大阶段之初始化阶段

啃下MyBatis源码 - MyBatis核心流程三大阶段之代理阶段(binding模块分析)

啃下MyBatis源码 - MyBatis核心流程三大阶段之数据读写阶段

啃下MyBatis源码 - MyBatis面试题总结

--------------------------------------------------------------------------------------------------------------------------

啃下MyBatis源码 - org.apache.ibatis.cache包源码分析

1.MyBatis缓存模块具体实现(BlockingCache)

2.MyBatis怎样给缓存数据确定一个唯一的key值

--------------------------------------------------------------------------------------------------------------------------

1.MyBatis缓存模块具体实现(BlockingCache)

       我们先来看一下cache的包结构:

       Cache:MyBatis的缓存接口

       PerpetualCache:缓存的实际存储类,使用一个HashMap作为缓存容器,实现了简单的缓存功能(非线程安全),get和put等操作都是直接调用HaspMap的相应方法实现。decorators包下其它Cache实现类都使用了装饰器模式,在该类的基础上添加了其他功能。

       BlockingCache/SynchronizedCache:通过在get/put方式中加锁,保证只有一个线程操作缓存

       FifoCache、LruCache:当缓存到达上限时候,通过FIFO或者LRU(最早进入内存)策略删除缓存

       ScheduledCache:在进行get/put/remove/getSize等操作前,判断缓存时间是否超过了设置的最长缓存时间

       SoftCache/WeakCache:通过JVM的软引用和弱引用来实现缓存,当JVM内存不足时,会自动清理掉这些缓存

       TransactionalCacheManager:用于管理CachingExecutor使用的二级缓存对象

       装饰器模式:允许向一个现有的对象添加新的功能,同时又不改变其结构(在这里只分析源码,等写完反射模块后会写一篇关于装饰器模式的博文,欢迎大家来踩哈)其实这些基于PerpetualCache实现的类都大同小异,下面我们就主要分析下和并发有关的BlockingCache(基于阻塞模型的缓存实现)类的源码,既然是和并发有关,那么这个类是如何获取数据的呢?我们来看一下它的getObject()方法:

public class BlockingCache implements Cache {
  //尝试获取锁的时间
  private long timeout;
  //被装饰的底层对象
  private final Cache delegate;
  //锁对象集,力度控制到key值
  private final ConcurrentHashMap<Object, ReentrantLock> locks;
  @Override
  public Object getObject(Object key) {
    acquireLock(key);//根据key获取对象,获得锁成功加锁,获取失败阻塞一定时间重试。
    Object value = delegate.getObject(key);
    if (value != null) {//获取数据成功则释放锁
      releaseLock(key);
    }
    return value;
   ...
}

       缓存模块的key值生成策略我们在下面会讲,跟进acquireLock()方法源码:

private void acquireLock(Object key) {
    //根据key值拿锁
    Lock lock = getLockForKey(key);
    //如果设置了尝试获取锁的时间,则调用tryLock的重载方法
    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();
    }
  }

       再跟进getLockForKey()方法:

private ReentrantLock getLockForKey(Object key) {
    //尝试把新锁加到locks集合中,如果添加成功,则使用新的锁。如果添加失败,则使用locks集合中创建过的锁
    return locks.computeIfAbsent(key, k -> new ReentrantLock());
}

       看到这里关于BlockingCache获取数据的流程就清晰了,BlockingCache类内部自己封装了一个ConcurrentHashMap<Object, ReentrantLock> locks的锁对象集,用来记录key值对应获取到的锁。在读缓存的时候,通过key值去locks集合中拿到对应的所对象,没有则进行创建并存入locks集合中。如果获取到所对象,则进行加锁操作,然后从底层缓存PerpetualCache中获取数据,最后释放锁。至于写数据就很简单了,直接写,判断下当前key是否持有锁,如果持有则将锁释放掉。

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

2.MyBatis怎样给缓存数据确定一个唯一的key值

       构成Key的因素有以下四条:

              1、mappedStatment的id

              2、指定查询结果集的范围(分页信息)

              3、查询所使用的SQL语句

              4、用户传递过来的SQL语句的实际参数值

       在哪里证明呢,在BaseExecutor中的createCacheKey方法中:

       我们知道了这个Key值的生成元素,那么它究竟是怎么存储和比较的呢?我们先来看一下CacheKey的部分源码:

public class CacheKey implements Cloneable, Serializable {
    ...
    private final int multiplier;//参与hash计算的乘法因子
    private int hashcode;//CacheKey的hash值,在update函数中计算出
    private long checksum;//校验和,hash值的和
    private int count;//updateList中元素的个数
    private List<Object> updateList;//判断CacheKey是否相等的判断元素的集合
    ...
}

       可以看到CacheKey类中共有四个比较参数,还有一个用来储存这些比较参数的集合,我们来看一下update()方法:

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

       原来MyBatis有自己的一套生成不重复Key的规则,具体的hashcode就是由:乘法因子*object的hash值+校验和*updateList中元素个数,说白了就是为了生成一个不重复的CacheKey。至于比较呢,我们看下equals()方法:

@Override
  public boolean equals(Object object) {
    if (this == object) {
      return true;
    }
    if (!(object instanceof CacheKey)) {
      return false;
    }
    final CacheKey cacheKey = (CacheKey) object;
    //先比hash值
    if (hashcode != cacheKey.hashcode) {
      return false;
    }
    //再比校验和
    if (checksum != cacheKey.checksum) {
      return false;
    }
    //再比updateList中元素的个数
    if (count != cacheKey.count) {
      return false;
    }
    //如果上面三个比较都相等,再比较updateList里存好的比较参数
    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;
  }

       先比hash值,如果hash值都不相同那就没得比了,对象肯定不相同。如果hash值相同,了解过hashMap底层原理的童鞋都知道是可能存在hash碰撞的,然后MyBatis比较了校验和,如果校验和也相同又比较了updateList中参数的个数。如果上面的这些参数都相同,最后把updateList集合中每个元素拿出来再一一比较,结果还是相同的话,则判定Key值相同。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值