5.一级缓存

1.mybatis 缓存概述

myBatis中存在两个缓存,一级缓存和二级缓存。

  • 一级缓存:也叫做会话级缓存,生命周期仅存在于当前会话,不可以直接关闭。但可以通过flushCache和localCacheScope对其做相应控制。
  • 二级缓存:也叫应用级性缓存,缓存对象存在于整个应用周期,而且可以跨线程使用。

关于二级缓存将在后续章节,详细说明。文本先聚焦一级缓存。首先来看如何才能命中一级缓存。

2.一级缓存的存储形式

一级缓存是以Map形式存在的key-value。存在于BaseExecutor的局部变量中,SqlSession -> Executor -> localCache 一对一的关系。可知一级缓存仅存在于当前会话。

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  protected Transaction transaction;
  protected Executor wrapper;

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  // 一级缓存存储形式
  protected PerpetualCache localCache;
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;

  protected int queryStack;
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    // 一级缓存存储形式
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }
  // ...  
}

// 一级缓存,存储的对象
public class PerpetualCache implements Cache {

  private final String id;

  // HashMap,key-value形式
  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }
  // ...  
}

3.一级缓存的命中场景

关于一级缓存的命中可大致分为两个场景

  • 第一满足特定命中参数
  • 第二不触发清空方法

3.1缓存命中参数

缓存的key为CacheKey对象,主要与以下几个参数有关:

  • 同一个会话
  • SQL相同
  • 参数相同
  • 相同的MapperStatement ID
  • RowBounds行范围相同
  • 相同的environmentId(防止切换数据库)
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    // MapperStatement ID
    cacheKey.update(ms.getId());
    // rowBounds分页参数有关
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    // 执行SQL
    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
      // 数据库环境ID,防止以上条件都相同切换数据库
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

3.2触发清空缓存

  • 手动调用clearCache
  • 执行提交回滚
  • 执行update
  • 配置flushCache=true
  • 缓存作用域为Statement
// sqlSession代码
public void clearCache() {
    executor.clearLocalCache();
}

// BaseExecutor代码
public void clearLocalCache() {
  if (!closed) {
    localCache.clear();
    localOutputParameterCache.clear();
  }
}

// BaseExecutor代码,回滚
public void rollback(boolean required) throws SQLException {
    if (!closed) {
        try {
            // 清除缓存
            clearLocalCache();
            flushStatements(true);
        } finally {
            if (required) {
                transaction.rollback();
            }
        }
    }
}

// BaseExecutor代码,提交
public void commit(boolean required) throws SQLException {
    if (closed) {
        throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 清除缓存
    clearLocalCache();
    flushStatements();
    if (required) {
        transaction.commit();
    }
}

// BaseExecutor代码,更新
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);
}

// BaseExecutor代码,查询时设置flushCacheRequired
// MapperXml文件配置flushCache=true,查询前会清除缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
    clearLocalCache();
}

// BaseExecutor代码,查询数据后。先添加进缓存,后又清除
// 设置<setting name="localCacheScope" value="STATEMENT"/>
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
    // 清除缓存
    clearLocalCache();
}

4.总结

一级缓存逻辑就存在于 BaseExecutor (基础执行器)里面。当会话接收到查询请求之后,会交给执行器的Query方法,在这里会通过 Sql、参数、分页条件等参数创建一个缓存key,在基于这个key去 PerpetualCache中查找对应的缓存值,如果有主直接返回。没有就会查询数据库,然后在填充缓存。一级缓存最终就是个HashMap

image-20210806000705672

缓存的清空对应BaseExecutor中的 clearLocalCache.方法。只要找到调用该方法地方,就知道哪些场景中会清空缓存了。

  • update: 执行任意增删改
  • select:查询又分为两种情况清空,前置清空:配置了flushCache=true。后置清空:配置了缓存作用域为statement 查询结束合会清空缓存。
  • commit:提交前清空
  • Rolback:回滚前清空

缓存的清空对应BaseExecutor中的 clearLocalCache.方法。只要找到调用该方法地方,就知道哪些场景中会清空缓存了。

  • update: 执行任意增删改
  • select:查询又分为两种情况清空,前置清空:配置了flushCache=true。后置清空:配置了缓存作用域为statement 查询结束合会清空缓存。
  • commit:提交前清空
  • Rolback:回滚前清空

注意:clearLocalCache 不是清空某条具体数据,而清当前会话下所有一级缓存数据(整个HashMap)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值