转载

Mybatis源码系列4-一级缓存

2020,你好

在哪里

每次当我从sqlsession工厂获取一个sqlsession时,都会创建一个BaseExecutor执行器,而一级缓存跟随执行器一起创建

///DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
        //创建一个执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx);
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

//configuration
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
//BaseExecutor
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;
  }
复制代码

也就说一个会话配一个一级缓存。 一级缓存的作用范围是sqlsesion, 其生命周期也跟随sqlsession生命周期

长什么样

一级缓存用PerpetualCache类表示,PerpetualCache 实现了Cache接口。而Cache 接口是Mybatis定义的缓存接口。PerpetualCache只是众多Cache 实现中的一种。

public class PerpetualCache implements Cache {
  private String id;

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

  public PerpetualCache(String id) {
    this.id = id;
  }
}
复制代码

可以看出,PerpetualCache 的本质就是HashMap。

如何用

首先得讲讲缓存的K值

CacheKey

在Mybatis中不管是一级缓存,还是二级缓存,都使用CacheKey 类对象作为K值。

CacheKey 值的相等判断,作为命中的决定因素就显得尤为重要了。

看CacheKey的创建

 public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    CacheKey cacheKey = new CacheKey();
    //1.statementId
    cacheKey.update(ms.getId());
    //2. rowBounds.offset
    cacheKey.update(rowBounds.getOffset());
    //3. rowBounds.limit
    cacheKey.update(rowBounds.getLimit());
     //4. SQL语句
    cacheKey.update(boundSql.getSql());
    //5. 将每一个要传递给JDBC的参数值也更新到CacheKey中
    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);
      }
    }
    //6. 环境id
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

//CacheKey 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);
  }
复制代码

从其创建可以看出CacheKey的决定条件:

statementId + rowBounds + 传递给JDBC的SQL + 传递给JDBC的参数值+Environment.id

每个条件都会被计算到hashcode 的值中。

开启一级缓存

默认是开启的

工作流程

当我们使用sqlsession 执行查询相关操作时, 最终会执行BaseExecutor.query。此时一级缓存开始工作

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


  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

    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;
  }
//从数据库中查询,并把结果缓存到一级缓存中,
 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;
  }
复制代码

流程总结:

  1. 首先根据条件创建CacheKey
  2. 根据CacheKey 去一级缓存中取值,命中,返回缓存中数据
  3. 未命中,去数据库中查询,并以CacheKey 为K将查询结果缓存起来。

清空缓存

一级缓存比较简单,没有所谓的过期清除,更新操作。这样意味着,我们在使用一级缓存时,要把一级缓存可能造成的数据不一致情况考虑进来。

一级缓存内数据存活期比较短暂。

清空时机:

1.同一个sqlsesion执行更新操作时,

public int update(MappedStatement ms, Object parameter) throws SQLException {
      //清空一级缓存。
    clearLocalCache();
    return doUpdate(ms, parameter);
  }
public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }
复制代码

2.当我在代码中主动调用sqlsesion#clearCache方法清空

public void clearCache() {
    executor.clearLocalCache();
  }
复制代码

3.当调用sqlseesion#close方法时, 此时直接将 一级缓存对象释放了。

public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }
  public void close(boolean forceRollback) {
    try {
     ...
    } catch (SQLException e) {
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;//直接把localCache置空
      localOutputParameterCache = null;
      closed = true;
    }
  }
复制代码

4.sqlsession 被回收,BaseExecutor ,一级缓存一并回收

禁用一级缓存

如果我们不想启用一级缓存怎么弄?

禁用一级缓存的奥秘就在BaseExecutor#query 方法中这两行中。

if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
...
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
复制代码

1.SQL级别

ms.isFlushCacheRequired==true 对应的配置文件中

<select id="queryTrastatusDateMax" resultType="String" flushCache="true">
复制代码

2.全局级别

configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT 对应配置文件中

<setting name="localCacheScope" value="STATEMENT" />
复制代码

总结

一级缓存相比与二级缓存比较简单。

总结起来:

  • 一级缓存实现类PerpetualCache ,本质是HashMap的一次封装
  • 一级缓存的作用范围SqlSession
  • 更新操作会清空一级缓存
原文  https://juejin.im/post/5e0cc2e7e51d4541563de67e
正文到此结束
Loading...