如何理解Mybatis源码中的Cache

发布时间:2021-09-14 10:36:08 作者:柒染
来源:亿速云 阅读:84

这篇文章将为大家详细讲解有关如何理解Mybatis源码中的Cache,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。

缓存的使用场景

缓存设计的要点

实践中可能存在的问题

缓存穿透

访问的缓存不存在,直接去访问数据库。通常查找的key没有对应的缓存,可以设计为返回空值,不去查找数据库。

缓存雪崩

大量的缓存穿透会导致有大量请求,访问都会落到数据库上,造成缓存雪崩。所以如果访问的key在缓存中找不到,不要直接去查询数据库,也就是要避免缓存穿透,可以设置缓存为永久缓存,然后通过后台定时更新缓存。也可以为缓存更新添加锁保护,确保当前只有一个线程更新数据。

Mybatis中的缓存分析与学习

MyBatis的一级缓存和二级缓存

一级缓存

//STATEMENT或者SESSION,默认为SESSION
<setting name="localCacheScope" value="STATEMENT"/>
//BaseExecutor.query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
   ...
    List<E> list;
    try {
      ...
      //先根据cachekey从localCache去查
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        //若查到localCache缓存,处理localOutputParameterCache
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        //从数据库查
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      //清空堆栈
      queryStack--;
    }
    ...
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
    	//如果是STATEMENT,清本地缓存
        clearLocalCache();
      }
    return list;
  }

二级缓存

<setting name="cacheEnabled" value="true" /> (或@CacheNamespace) 
<cache/>,<cache-ref/>或@CacheNamespace
//每个select设置
  <select id="queryDynamicFlightVOs" resultType="com.ytkj.aoms.aiis.flightschedule.vo.DynamicFlightVO" useCache="false">

如果配置了二级缓存,那么在获取Executor的时候会返回CachingExecutor

//Configuration.java
  //产生执行器
  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    //这句再做一下保护,,防止粗心大意的人将defaultExecutorType设成null?
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    //如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    //此处调用插件,通过插件可以改变Executor行为
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

开启了二级缓存后,会先使用CachingExecutor查询,查询不到在查一级缓存。 如何理解Mybatis源码中的Cache

   public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        //namespace配置作用
        Cache cache = ms.getCache();
        if (cache != null) {
            //是否执行刷新缓存操作
            this.flushCacheIfRequired(ms);
            //useCache标签作用
            if (ms.isUseCache() && resultHandler == null) {
                this.ensureNoOutParams(ms, parameterObject, boundSql);
                List<E> list = (List)this.tcm.getObject(cache, key);
                if (list == null) {
                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
                    this.tcm.putObject(cache, key, list);
                }

                return list;
            }
        }

        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
    }

注意

装饰器模式

通过装饰器模式,我们可以向一个现有的对象添加新的功能,同时不改变其结构。
Mybatis中不同类型的缓存,正是使用了此类设计。

PerpetualCache实现了Cache接口,完成了基本的Cache功能,其他的装饰类对其进行功能扩展。以LruCache为例:

public class LruCache implements Cache {

  //持有实现类
  private final Cache delegate;
  //额外用了一个map才做lru,但是委托的Cache里面其实也是一个map,这样等于用2倍的内存实现lru功能
  private Map<Object, Object> keyMap;
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }
 
    .....
}
LruCache的具体实现 [Least Recently Used]

LruCache使用了一个LinkedHashMap作为keyMap,最多存1024个key的值,超过之后新加对象到缓存时,会将不经常使用的key从keyMap中移除,并且删除掉缓存中对应的key。利用了LinkedHashMap的特性:

//LinkedHashMap的get方法
  public V get(Object key) {
        Node<K,V> e;
        if ((e = getNode(hash(key), key)) == null)
            return null;
        if (accessOrder)
            afterNodeAccess(e);
        return e.value;
    }
  public void setSize(final int size) {
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      //核心就是覆盖 LinkedHashMap.removeEldestEntry方法,
      //返回true或false告诉 LinkedHashMap要不要删除此最老键值
      //LinkedHashMap内部其实就是每次访问或者插入一个元素都会把元素放到链表末尾,
      //这样不经常访问的键值肯定就在链表开头啦
      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
            //根据eldestKey去缓存中删除
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }
  
   private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    //keyMap是linkedhashmap,最老的记录已经被移除了,然后这里我们还需要移除被委托的那个cache的记录
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }
Mybatis Cache类型与特性

关于如何理解Mybatis源码中的Cache就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

推荐阅读:
  1. MyBatis源码浅析
  2. MyBatis Cache配置

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

mybatis cache

上一篇:将HTML5元素定义为块元素的方法

下一篇:C++中继承方式和访问限定符有什么关系

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》