MyBatis的Cache机制解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_31922571/article/details/79865210

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
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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
  • 2
  • 3
  • 4
  • 5

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72

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;
  }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

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();
    }
  }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51

2.2 一级缓存的刷新

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

2.3 一级缓存的配置

mybatis-config.xml

<settings>
    //...
    <setting name="localCacheScope" value="SESSION | STATEMENT"/>
    //...
</settings>
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

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

//...
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
//...
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

  }
//...
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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);
    }
   //...
  }
   
   
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

3.1 二级缓存的配置

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

<settings>
    //...
    <setting name="cacheEnabled" value="true|false" />
    //...
</settings>
   
   
  • 1
  • 2
  • 3
  • 4
  • 5

Mapper XML配置
xxx.mapper.xml

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

根据缓存器配置创建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时回收它。

Mybatis Cache参数

  • flushInterval(刷新间隔)可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
  • size(引用数目)可以被设置为任意正整数,要记住你缓存的对象数目和你运行环境的可用内存资源数目。默认值是1024。
  • readOnly(只读)属性可以被设置为true或false。只读的缓存会给所有调用者返回缓存对象的相同实例。因此这些对象不能被修改。这提供了很重要的性能优势。可读写的缓存会返回缓存对象的拷贝(通过序列化)。这会慢一些,但是安全,因此默认是false。

如下例子:

<cache  eviction="FIFO"  flushInterval="60000"  size="512"  readOnly="true"/>
   
   
  • 1

这个更高级的配置创建了一个 FIFO 缓存,并每隔 60 秒刷新,存数结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此在不同线程中的调用者之间修改它们会导致冲突。

可用的收回策略有, 默认的是 LRU:

1.LRU – 最近最少使用的:移除最长时间不被使用的对象。
2.FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
3.SOFT – 软引用:移除基于垃圾回收器状态和软引用规则的对象。
4.WEAK – 弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
   
   
  • 1
  • 2
  • 3
  • 4

猜你喜欢

转载自blog.csdn.net/qq_31922571/article/details/79865210