源码解析MyBatis缓存

MyBatis缓存

说到MyBatis缓存我们应该知道其分为一级缓存和二级缓存,一级缓存对应sqlsession二级缓存对应namespace,缓存在进行增删改时进行刷新。但是具体对应源代码你知道是如何进行刷新的吗?我们一起来看下源代码。

MyBatis的查询起始于DefaultSqlSession的select,这里我们能看到DefaultSqlSession将查询交给执行器进行查询,这里查询的方法有多个,但是大同小异最终都是交给执行器,这里以select方法为例进行查看。

@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    MappedStatement ms = configuration.getMappedStatement(statement);
    executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

这里我们看下query方法,这里我们能看到query方法的实现类只有BaseExecutor和CachingExecutor两个方法,所有的执行器都继承自BaseExecutor,这里只有CachingExecutor进行了重写,这里重写既是对二级缓存的使用。所以开启二级缓存的执行器会通过CachingExecutor进行处理。

这里我们看下CachingExecutor类的query方法在这里插入图片描述

//这是CachingExecutor的变量,它是二级缓存的临时存储位置,临时、临时、临时重要的事说三遍
private TransactionalCacheManager tcm = new TransactionalCacheManager();

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  //这里进行缓存的key计算
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
  
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  throws SQLException {
  //这里才是二级缓存的真正位置,MappedStatement对象中,这样保证了每次新建执行器,缓存不被删除
  Cache cache = ms.getCache();
  if (cache != null) {
    //设置清空缓存属性,清空缓存
    flushCacheIfRequired(ms);
    //设置useCache属性
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, parameterObject, boundSql);
      //这里将缓存加入到TransactionalCache对象,如果不存在加入,存在直接查找
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      //这里如果二级缓存查询到数据,直接返回,查询不到会调用BaseExecutor类的查询方法,里面对应         //有一级缓存的调用,所以二级缓存的优先级大于一级缓存的优先级
      if (list == null) {
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        //这里如果二级缓存没数据,会将查询结果刷入临时缓存,最终将临时缓存刷入二级缓存
        tcm.putObject(cache, key, list); 
      }
      return list;
    }
  }
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

计算缓存的键值上面是CachingExecutor类的方法,下面是BaseExecutor类的方法。CachingExecutor方法初始化时会传入一个执行器,这里最终都会调用BaseExecutor的key生成方法。

@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
  
  
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  CacheKey cacheKey = new CacheKey();
  //MappedStatement的id,这里是namespace,会在其它文章中介绍
  cacheKey.update(ms.getId());
  //偏移量
  cacheKey.update(rowBounds.getOffset());
  //limit分页字段
  cacheKey.update(rowBounds.getLimit());
  //sql
  cacheKey.update(boundSql.getSql());
  //参数
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
  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;
}

//这里是一个更新方法,实质是一个hashCode计算,每个查询的hashCode都会有,根据查询的所有元素进行计算
public void update(Object object) {
  int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object); 

  count++;
  checksum += baseHashCode;
  baseHashCode *= count;

  hashcode = multiplier * hashcode + baseHashCode;
  //所有参与计算的变量都会存入集合
  updateList.add(object);
}

这里是查询临时缓存,如果没有,会将缓存加入进去。

private TransactionalCache getTransactionalCache(Cache cache) {
  TransactionalCache txCache = transactionalCaches.get(cache);
  //加入缓存,这里保证了二级缓存能够对应namespace,没有缓存会将查到的缓存刷进去
  if (txCache == null) {
    txCache = new TransactionalCache(cache);
    transactionalCaches.put(cache, txCache);
  }
  return txCache;
}

TransactionalCache这里是获取缓存对象,根据key进行获取。这里既是二级缓存对应的缓存获取。

@Override
public Object getObject(Object key) {
  Object object = delegate.getObject(key);
  if (object == null) {
    entriesMissedInCache.add(key);
  }
  if (clearOnCommit) {
    return null;
  } else {
    return object;
  }
}

BaseExecutor这里是一级缓存对应如下。

//一级缓存对应位置
protected PerpetualCache localCache;
//存入查询条件
protected PerpetualCache localOutputParameterCache;
//初始化
protected BaseExecutor(Configuration configuration, Transaction transaction) {
  this.transaction = transaction;
  this.deferredLoads = new ConcurrentLinkedQueue<DeferredLoad>();
  this.localCache = new PerpetualCache("LocalCache");
  this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
  this.closed = false;
  this.configuration = configuration;
  this.wrapper = this;
}

@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();
    }
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      clearLocalCache();
    }
  }
  return list;
}

这里对应为什么一级缓存insert、update或者不同事务会存在清空。这里能够看到事务的提交和回滚都会进行缓存的清空,update之前也会进行清空。

@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  //清空缓存
  clearLocalCache();
  return doUpdate(ms, parameter);
}

@Override
public void commit(boolean required) throws SQLException {
  if (closed) {
    throw new ExecutorException("Cannot commit, transaction is already closed");
  }
  //清空缓存
  clearLocalCache();
  flushStatements();
  if (required) {
    transaction.commit();
  }
}


@Override
public void rollback(boolean required) throws SQLException {
  if (!closed) {
    try {
      //清空缓存
      clearLocalCache();
      flushStatements(true);
    } finally {
      if (required) {
        transaction.rollback();
      }
    }
  }
}

这里是二级缓存的缓存处理,会先清空一级缓存,在处理二级缓存

//CachingExecutor事务提交
@Override
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();
}

//TransactionalCache缓存处理,这里注意下delegate是Cache缓存不是Executor执行器
private void flushPendingEntries() {
  for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
    //这里的delegate既是我们通过MappedStatement.getCache获取到的缓存,将临时缓存刷入二级缓存
    delegate.putObject(entry.getKey(), entry.getValue());
  }
  for (Object entry : entriesMissedInCache) {
    if (!entriesToAddOnCommit.containsKey(entry)) {
      delegate.putObject(entry, null);
    }
  }
}

这里是二级缓存修改时进行进行清空缓存。

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

private void flushCacheIfRequired(MappedStatement ms) {
  //获取缓存
  Cache cache = ms.getCache();
  //缓存存在,且FlushCache设置为是清空缓存
  if (cache != null && ms.isFlushCacheRequired()) {      
    tcm.clear(cache);
  }
}

CacheKey重写了equals方法,他的hashcode、updateList及其它属性相等才能相等。

@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 (!ArrayUtil.equals(thisObject, thatObject)) {
      return false;
    }
  }
  return true;
}

这里cache有多种,我们可以根据需要进行设置。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值