MyBatis(3.4.2)的Cache机制完全解析

### 1. MyBatis缓存机制的核心构件

1.1 Cache接口

接口简单明了,Cache的基本操作;put/get/remove/clear。

public interface Cache {
  String getId();//分组ID
  void putObject(Object key, Object value);//put
  Object getObject(Object key);//get
  Object removeObject(Object key);//remove
  void clear();//clear
  int getSize();//可选 core不再使用
  ReadWriteLock getReadWriteLock();//3.2.6后已废弃

}
1.2 Cache的基本实现:PerpetualCache

通过HashMap实现缓存管理。

public class PerpetualCache implements Cache {
  private String id;//ID进行分组
  private Map<Object, Object> cache = new HashMap<Object, Object>();//数据缓存
//...实现接口,就是对HashMap的操作
}
1.3 CacheKey的构成

核心功能,可以动态更新Key的hash值。因为底层缓存数据是基于HashMap实现的,在比对value|key是否存在时,会调用hashCode方法和equals。
CacheKey覆写了hashcode和equals。
比较顺序:hashCode–>checksum–>count–>updateList,只要有一个不等则说明不是相同的Key。

public class CacheKey implements Cloneable, Serializable {

  //...
  private static final int DEFAULT_MULTIPLYER = 37;
  private static final int DEFAULT_HASHCODE = 17;

  private int multiplier;
  private int hashcode;
  private long checksum;
  private int count;
  private List<Object> updateList;

  public CacheKey() {
    this.hashcode = DEFAULT_HASHCODE;
    this.multiplier = DEFAULT_MULTIPLYER;
    this.count = 0;
    this.updateList = new ArrayList<Object>();
  }
  //...
  //hashcode的计算方法
   private void doUpdate(Object object) {
    int baseHashCode = object == null ? 1 : object.hashCode();

    count++;
    checksum += baseHashCode;
    baseHashCode *= count;

    hashcode = multiplier * hashcode + baseHashCode;//hashcode的算法

    updateList.add(object);
  }

  //...
  //比较2个CacheKey是否相等
  @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) {
      return false;
    }
    if (checksum != cacheKey.checksum) {
      return false;
    }
    if (count != cacheKey.count) {
      return false;
    }

    for (int i = 0; i < updateList.size(); i++) {
      Object thisObject = updateList.get(i);
      Object thatObject = cacheKey.updateList.get(i);
      if (thisObject == null) {
        if (thatObject != null) {
          return false;
        }
      } else {
        if (!thisObject.equals(thatObject)) {
          return false;
        }
      }
    }
    return true;
  }
  //...
}
1.4 MyBatis中CacheKey的创建
 @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
   //...
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    //...
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        //...
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

Key的构成:cacheKey=ID + offset + limit + sql + parameterValues + environmentId

2. MyBatis的一级缓存

一级缓存,即本地缓存,永久缓存,Mybatis自行控制缓存的读写删,在执行器(BaseExecutor)中使用,执行器是在SqlSessionFactory.openSession()后创建的SqlSession中执行SQL语句时创建的,所以一级缓存的生命周期等同于SqlSession,也就是说是Session级的缓存。

2.1 一级缓存的创建、清空和销毁

MyBatis在commit和rollback时都会清空一级缓存,在SqlSession关闭时也会主动清空缓存,可以被GC掉。

 protected BaseExecutor(Configuration configuration, Transaction transaction) {
   //...
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
   //...
  }

   @Override
  public void close(boolean forceRollback) {
    try {
     //...
    } finally {
     //...
      localCache = null;
      localOutputParameterCache = null;
      //...
    }
  }

   @Override
  public void commit(boolean required) throws SQLException {
   //...
    clearLocalCache();
    //...
    if (required) {
      transaction.commit();
    }
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
       //...
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }


  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
2.2 一级缓存的刷新

缓存必然是针对Select查询操作提高查询效率设计的,在Mapper XML的配置文件中可以设置 flushCache=”ture”(默认为false)来刷新一级缓存(也包括二级缓存),任何时候只要语句被调用,都会清空一级缓存和二级缓存。

2.3 一级缓存的配置

mybatis-config.xml

<settings>
    //...
    <setting name="localCacheScope" value="SESSION | STATEMENT"/>
    //...
</settings>

localCacheScope默认为SESSION,缓存一个会话中执行的所有的查询结果,如果设置为STATEMENT,则在同一个会话中的不同的调用将不会共享数据,在调用完毕后会清理一级缓存。

//...
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
//...
2.4 嵌套查询的优化

MyBatis利用一级缓存机制加速重复嵌套查询。

private static class DeferredLoad {

   //...
    public boolean canLoad() {
      return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }

    public void load() {
   //...
      List<Object> list = (List<Object>) localCache.getObject(key);
    //...
    }

  }
//...

3. MyBatis的二级缓存

实现 org.mybatis.cache.Cache 接口都可以作为MyBatis的二级缓存;二级缓存的Cache对象由Configuration进行管理,而
每次构建SqlSessionFactory对象时都会创建新的Configuration对象,因此,二级缓存的生命周期与SqlSessionFactory是相同的。基于Mapper XML 配置,在创建每个MapperedStatement对象时,都会根据其所属的namespace名称空间,给其分配Cache缓存实例。

二级缓存由CachingExecutor负责管理维护,开启了二级缓存后,Executor将使用CacheExecutor,基于装饰器模式对配置的Executor进行包装,扩展了缓存功能。

 public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
   //...
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
   //...
  }
3.1 二级缓存的配置

mybatis-config.xml配置,false则不启用二级缓存,ture则自动使用CachingExecutor。

<settings>
    //...
    <setting name="cacheEnabled" value="true|false" />
    //...
</settings>

Mapper XML配置
xxx.mapper.xml

<cache
  type="PERPETUAL"//
  eviction="LRU"//算法
  flushInterval="60000"//刷新间隔,间隔60秒清空缓存,被动触发非定时器轮询
  size="512"//大小
  readOnly="false"//true:返回cache结果的克隆对象
  blocking="false"
 />

根据缓存器配置创建Cache实例,以namespace为CacheId,一个namespace对应一个Cache实例。
可以通过实现 org.mybatis.cache.Cache 接口创建自定义的缓存器,比如基于Redis或者Memcached实现分布式缓存。

3.2 MyBatis内置的二级缓存算法

Mybatis的所有Cache算法都是基于装饰器模式对PerpetualCache扩展增加功能。

FIFO:先入先出,基于LinkedList实现;

LRU:最近最少使用,基于LinkedHashMap实现,在put的时候,自动移除最少使用缓存对象;

SOFT:对Cache的value进行SoftReference包装;当缓存对象是Soft reference可达时,gc会向系统申请更多内存,而不是直接回收它,当内存不足的时候才回收它;

WEAK:对Cache的value进行WeakReference包装;WeakReference不会强制对象保存在内存中。它拥有比较短暂的生命周期,允许你使用垃圾回收器的能力去权衡一个对象的可达性。在垃圾回收器扫描它所管辖的内存区域过程中,一旦gc发现对象是weakReference可达,就会把它放到ReferenceQueue中,等下次gc时回收它。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值