mybatis二级缓存学习

二级缓存也称作应用缓存,与一级缓存不同的是它的作用范围是整个应该程序,而且是可以跨线程使用。所以二级缓存有更高的命中率,适合缓存一些修改较少的数据。
一般情况,二级缓存也是默认关闭的,可以直接在config.xml配置文件打开,也可以通过某个实体的mapping.xml打开,只要在里面加上
< cache></ cache>就可以了。
我们先来看访问二级缓存的执行流程。
在这里插入图片描述
简单来说,CachingExecutor里面的query方法做了大部分的工作,现在看看这个方法的源码

@Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

第一行代码获得一个cache对象,而获取的条件则是根据你的配置是否打开了二级缓存以及namespace等。flushCacheIfRequired(ms)方法则是清空二级缓存,判断条件是看你是否手动设置了需要清空缓存。
接下来便是是getObject方法,也是访问二级缓存的map,如果返回的list是空,则继续查询数据库,然后通过put放入二级缓存中。
其中在put的过程中还有一个缓存区的概念,并不是把数据库查出来的值直接放入二级缓存的,而是先放入缓存区,只有在session提交之后,才会把缓存区的数据库放入二级缓存。缓存区当然也是一个map,而它的key则是一个cache对象,也就是二级缓存。这个设计是为了防止update的同时query导致查询的数据不一致。因为在update还没提交session的情况下,二级缓存是没有被清空的,只是用了一个标记来说明已经清空了。

  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    /**这里就是标记是否已经进行了修改的操作,
    如果是则返回null,表示二级缓存已被清空,但实际上二级缓存
    仍然有值,只有session提交后才真的清空*/
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

我们可以继续看看update的代码,是不是真的没有清空操作

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

这里调用了flushCacheIfRequired(ms)方法,再来看这个方法干了什么。其实就调用了clear()方法

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

我们可以看到,他把clearOnCommit 设置为true,这就是上面用到的判断条件,同时第二行代码则是清空缓存区。
那么我们再来看看缓存的存取过程还做了什么。
在mybatis中有一个Cache接口类,也就是上面CachingExecutor获取到的对象,代码如图

很简单的方法,相信即便不看注释也能知道是用来干嘛的。其中接口的主要实现是PerpetualCache类,里面也是一个HashMap,也就是二级缓存的真正的实现。
在对map操作的时候,mybatis采用了一个叫做责任链的设计模式,其中的节点有
在这里插入图片描述
每个节点都做一些自己的事情,然后继续传入选一个节点,就形成了一条链,这里每个节点都是用了装饰器模式。我们可以来看看线程同步节点的源码,

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

两个主要的方法,其实里面只是通过synchronized加锁,然后就传入下一个节点了。我们再来看下一个节点,序列化的节点代码

  @Override
  public void putObject(Object key, Object object) {
    if (object == null || object instanceof Serializable) {
      delegate.putObject(key, serialize((Serializable) object));
    } else {
      throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
    }
  }

  @Override
  public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    return object == null ? null : deserialize((byte[]) object);
  }

这两个方法则加上了序列化和反序列化的逻辑,然后继续交给下一个节点处理。这里就有一个问题,为什么一定要序列化呢,这是为了防止获取对象时,获取到同一个对象。
最后我们打个断点跑一下代码
在这里插入图片描述
可以看到依次访问了这么几个cache。对了还有二级缓存的命中条件
在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值