mybatis的一级缓存

目录

1. 什么是缓存

2. MyBatis一级缓存

3. 源码分析

3.1 一级缓存的保存与使用

3.2 一级缓存的清空

3.3 一级缓存的key


1. 什么是缓存

        缓存,即存储在内存中的临时数据。对于一些数据,如果它们经常被访问到并且在一定时间内不会被改变,那么可以考虑将其缓存下来以提高查询的效率。对于有缓存的数据的完整访问过程如下:

  1. 先查看缓存中是否已经有数据,如果有直接取出并返回;
  2. 如果缓存中没有数据,则查询数据库,并将查询到的数据写入到缓存以备下次使用;
  3. 下次再来访问同样数据的时候,由于缓存中已经有数据,无需再次访问数据库,达到提高效率的目的。

        之所以可以使用这种思路是因为内存的访问速度是很快的,远远快过访问网络或者磁盘的速度。

2. MyBatis一级缓存

        MyBatis的一级缓存默认开启,是属于每一个sqlsession的。当满足以下条件的时候,才可以命中缓存:

  • 相同的 sql 和 参数
  • 必须是在一个会话 Session当中
  • 必须是执行 相同的方法
  • 必须是相同的 namespace (同一个命名空间 -> 同一个mapper文件)
  • 不能够在查询之前执行 clearCache
  • 中间不能执行 任何 update ,delete ,insert (会将SqlSession中的数据全部清空)

3. 源码分析

3.1 一级缓存的保存与使用

        首先,mybatis最终通过BaseExecutor.query()方法操作数据库(追踪源码即可以发现)。源码如下:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @SuppressWarnings("unchecked")
  @Override
  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());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      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);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

        在上面的程序中,直接可见的流程如下:

  1. 生成key,由于缓存的底层数据结果是map,以k-v结构存储。
  2. 带着这个key,list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;去判断缓存中是否存在,
    1. 如果存在,返回数据。
    2. 如果不存在,调用queryFromDatabase查询数据库,将结果赋值给list返回。

        重点来看一级缓存空间是谁,以及如何存储,即queryFromDatabase方法的源码:

  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

        其中使用doQuery方法查询数据库,得到结果,在返回结果之前,先通过localCache.putObject(key, list);方法将结果保存,显然,这个属性就是我们的一级缓存,我们可以点进其类型定义中就会发现,其本质就是一个hash:  Mapprivate Map<Object, Object> cache = new HashMap<Object, Object>();

        至此,一级缓存的查询与插入的时机已经清楚,接下来就看一级缓存什么时候清除。

3.2 一级缓存的清空

        在上面的源码中可以发现有这样的一个方法:clearLocalCache();,根据命名就可以很容易地确定,这就是清空缓存的方法,所以只需要找到何时调用了这个方法,就可以知道一级缓存何时清空。总结如下:

  • update时,一级缓存会被清空。delete和insert都是调用这个update。可以从SqlSession的insert、update、delete方法跟踪。
  • LocalCacheScope.STATEMENT时,一级缓存会被清空。在BaseExecutor里的query方法中:
  • 事务提交回滚时,一级缓存会被清空。
  • flushCache="true"时,一级缓存会被清空。

3.3 一级缓存的key

        我们现在来追踪createCacheKey()方法来查看一级缓存的key是如何生成,

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

         代码较长,但是这里只是key的生成逻辑,我们并不关心,我们只关心key的结构,很显然,它被定义在CacheKey类中,所以只要知道了这个类的属性,就可以知道key如何构成,其所以属性和toString方法如下:

  private static final long serialVersionUID = 1146682552656046210L;

  public static final CacheKey NULL_CACHE_KEY = new NullCacheKey();

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

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

  @Override
  public String toString() {
    StringBuilder returnValue = new StringBuilder().append(hashcode).append(':').append(checksum);
    for (Object object : updateList) {
      returnValue.append(':').append(ArrayUtil.toString(object));
    }
    return returnValue.toString();
  }

于是,我们得到了key的结构:id + offset + limit + sql + param value + environment id。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值