Mybatis学习系列(十):缓存

mybatis的缓存主要是分为两种:一级缓存和二级缓存:

一级缓存:一直开启,不能关闭,默认是session级别的,但是可以设置成statement级别。

session级别:表现为---MyBatis执行SQL语句之后,这条语句就是被缓存,以后再执行这条语句的时候,会直接从缓存中拿结果,而不是再次执行SQL。但是在两个相同的查询之间,如果当前数据被修改,会进行两次查询。作用域是SqlSession级别。

1.在同一个SqlSession中相同的查询,只会走一次数据库查询,第二次走缓存

2.在同一个SqlSession中不同的查询,走两次数据库查询

3.在不同的SqlSession相同的查询,走两次数据库查询

4.在同一个SqlSession中相同的查询,如果在两次查询之前,修改当前数据,一级缓存会被清空,走两次数据库查询

我们看一下这个一级缓存在代码中是如何起作用的:

list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }

在Executor(BaseExecutor)的查询方法中,我们可以看到当resultHandler 为空时,我们直接从localCache中获取数据

我们看一下这个localCache的数据结构是个PerpetualCache对象,实现了Cache接口,其中有一个字段cache

private Map<Object, Object> cache = new HashMap<Object, Object>();

这里可以看到是Map类型的。

然后我们可以看到在查询方法里面,会把查询结果放到缓存里面

我们看到当设置的是stament级别时,每次操作完成都会清空一级缓存

那么为什么在同一session下,相同操作中间有修改动作的时候,缓存就无效呢?我们可以看一下修改方法里面做了什么

这里我们看到在执行修改操作之前,清空了当前session的一级缓存。

 public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

二级缓存:可以关闭,默认是开启的,作用域是全局的,但是二级缓存在SqlSession关闭或提交之后才会生效,也就是作用在两个session之间的。

1.在setting中配置cacheEnabled参数,默认为true,可关闭(false)

2.在mapper.xml的select节点上,设置useCache参数,默认是true开启,可关闭(false)

3.在mapper.xml中,添加<cache /> 标签 可指定缓存类型等

我们依然来看一下二级缓存在源码中的使用:

1.解析:是在我们解析每一个mapper.xml文件的时候,解析cache标签

详细看一下cache标签的解析

private void cacheElement(XNode context) throws Exception {
    if (context != null) {
      //这里获取制定的缓存类型 如果没有就设定默认的
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      Long flushInterval = context.getLongAttribute("flushInterval");
      Integer size = context.getIntAttribute("size");
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      boolean blocking = context.getBooleanAttribute("blocking", false);
      Properties props = context.getChildrenAsProperties();
      //创建缓存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

//创建缓存
public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    //添加到configuration中
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

然后在解析mapper的SQL语句时:这里解析当前方法是否开启useCache,默认查询是开启

boolean useCache = context.getBooleanAttribute("useCache", isSelect);

然后把cache设置到mapperdStatement

当我们设置了cache的时候,我们可以看到生成的Executor会被包一层CachingExecutor

那我们看一下CachingExecutor的query方法:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    //这里获取到对应的缓存cache
    Cache cache = ms.getCache();
    if (cache != null) {
      flushCacheIfRequired(ms);
      //如果当前查询开启了缓存  并且resultHandler 为空  走二级缓存
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        //从缓存中获取数据
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          //如果没有数据走数据库查询
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //把数据存到缓存中
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

我们看一下获取缓存数据的方法:

//TransactionalCacheManager
public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

//TransactionalCache
public Object getObject(Object key) {
    //这里通过内部的cache实例  获取数据
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

而把数据放到缓存里面的操作是:这里只是添加到了一个map中,并没有放到内部cache实例中

public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

这里其实是通过sqlsession的commit方法进行提交到缓存的。我们看下代码

//DefaultSqlSession
public void commit(boolean force) {
    try {
      //调用executor的commit
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

//调用executor的commit
//这里有缓存,所以是CachingExecutor
public void commit(boolean required) throws SQLException {
    delegate.commit(required);
    //缓存就是在这里提交的
    tcm.commit();
  }

//TransactionalCacheManager
public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

//TransactionalCache
public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    //刷新缓存
    flushPendingEntries();
    reset();
  }

//刷新缓存
private void flushPendingEntries() {
    //循环所有的entriesToAddOnCommit   map数据,添加到缓存cache中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

总结:从上面的解析可以看到,如果我们开启了二级缓存,会先从二级缓存中获取数据,如果二级缓存里面没有,这从一级缓存中获取,一级缓存如果没有数据,查询数据库;拿到结果后,把数据库存储到一级缓存中,当sqsession执行commit的时候把数据放到二级缓存中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值