mybatis源码分析(九):Executor及缓存

Executor及缓存

在这里插入图片描述

mybatis提供了缓存查询结果的功能,缓存功能主要由Executor来提供,所以下面首先看下Executor接口

Executor

// 执行update delete等操作
int update(MappedStatement ms, Object parameter) throws SQLException;

// 执行select
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

<E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;

// 批量执行sql
List<BatchResult> flushStatements() throws SQLException;

// 提交事务
void commit(boolean required) throws SQLException;

// 回滚事务
void rollback(boolean required) throws SQLException;

// 创建缓存中的Key
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);

// 判断对应的缓存是否存在
boolean isCached(MappedStatement ms, CacheKey key);

// 清空本地缓存
void clearLocalCache();

// 清空一级缓存
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);

Transaction getTransaction();

void close(boolean forceRollback);

boolean isClosed();

void setExecutorWrapper(Executor executor);

BaseExecutor

BaseExecutor实现了Executor的所有方法,并且通过模板设计模式,将query,update,queryCursor,flushStatements等方法真正从数据库中执行对应操作的逻辑抽出来,由其子类实现
这里看下query方法

重要属性

// 事务
protected Transaction transaction;
// 延迟加载相关
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
// 本地缓存
protected PerpetualCache localCache;
// 本地缓存,缓存存储过程中的输出参数
protected PerpetualCache localOutputParameterCache;
// 全局配置
protected Configuration configuration;
// 当前的查询嵌套层数
protected int queryStack;
// 是否已经关闭
private boolean closed;

一级缓存

BaseExecutor中实现了对一级缓存的操作

query

首先看下BaseExecutor的query方法

public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  // 获取使用占位符?替换之后的sql
  BoundSql boundSql = ms.getBoundSql(parameter);
  // 创建缓存key
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  // 调用重载方法进行查询
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
createCacheKey

下面看下createCacheKey

public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  CacheKey cacheKey = new CacheKey();
  // 1. 使用MappedStatement的id更新cacheKey
  cacheKey.update(ms.getId());
  // 2. 使用rowbounds的offset和Limit来更新cacheKey
  cacheKey.update(rowBounds.getOffset());
  cacheKey.update(rowBounds.getLimit());
  // 3. 使用boundSql中已经使用?替换后的sql来更新cacheKey
  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
      cacheKey.update(value);
    }
  }
  // 使用enviroment的id来更新cacheKey
  if (configuration.getEnvironment() != null) {
    // issue #176
    cacheKey.update(configuration.getEnvironment().getId());
  }
  return cacheKey;
}

在更新cacheKey的过程中,主要是使用入参的hash值来更新cacheKey,同时会将入参放到一个列表中

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的组成来自如下几个部分:

  1. MappedStatement的id
  2. Rowbounds的offset和limit
  3. BoundSql解析后的sql
  4. 输入参数的值
  5. Environment的key
    下面看下CacheKey的equals方法
public boolean equals(Object object) {
  // 1. 比较引用
  if (this == object) {
    return true;
  }
  // 2. 比较类型
  if (!(object instanceof CacheKey)) {
    return false;
  }

  final CacheKey cacheKey = (CacheKey) object;

  // 3. 比较hashCode
  if (hashcode != cacheKey.hashcode) {
    return false;
  }
  // 4. 比较checkSum
  if (checksum != cacheKey.checksum) {
    return false;
  }
  // 5. 比较cacheKey更新的次数
  if (count != cacheKey.count) {
    return false;
  }

  // 6. 依次比较每个入参
  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;
}
一级缓存查询
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.");
  }
  // queryStask记录嵌套查询的层数
  // 如果当前不是嵌套查询,并且在select标签上配置了flushCache,那么会清空一级缓存
  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();
    // 当前一级缓存的级别是STATEMENT的,缓存只在此次statement中有效,执行完毕就会清空缓存
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

接着看下queryFromDatabase

private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // 首先会在缓存中增加一个当前key的占位符
  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;
}

update

  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);
}

一级缓存总结

我们知道每个SqlSession都包含一个Executor,每个Executor自己维护一份属于自己的一级缓存,因此一级缓存是SqlSession级别的,SqlSession之间不会共享缓存

二级缓存

从之前分析mybatis执行流程的源码中,我们知道,在获取SqlSession时,会重新创建一个Executor

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); // may have fetched a connection so lets call close()
    throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}
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);
    }
    // 这里会判断是否开启了缓存
    // 这里是根据mybatis-config.xml中的cacheEnabled属性来判断是否开启二级缓存的
    if (cacheEnabled) {
      // 通过装饰者设计模式来对Executor的功能进行增强,增加二级缓存的处理逻辑
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

query

下面看下CacheExecutor的query方法

  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  // 首先解析BoundSql
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  // 创建缓存key
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  // 执行查询
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
  // 获取当前MappedStatement使用的缓存对象
  // 从这里可以看出缓存是和MappedStatement绑定的,而MappedStatment是全局共享的,因此二级缓存是全局范围的
  Cache cache = ms.getCache();
  if (cache != null) {
    // 判断MappedStatement是否配置了flushCache,如果配置了,会清空二级缓存
    flushCacheIfRequired(ms);
    // 当前MappedStatement使用缓存,并且没有配置ResultHandler
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      // 获取缓存,这里需要注意并没有直接从Cache对象中获取缓存,而是通过TransactionalCacheManager
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
        // 如果缓存为空,那么会将请求委托给包裹的Executor,一般是BaseExecutor的子类,因此会经过一级缓存
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        // 将查询结果添加到缓存中,这里也需要注意并不是将数据直接添加到Cache中,而是对TransactionalCacheManager进行操作
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

update

@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
  // 在将请求交给delegate之前,先清空缓存
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
  Cache cache = ms.getCache();
  // 使用TransactionalCacheManager来清空缓存
  if (cache != null && ms.isFlushCacheRequired()) {
    tcm.clear(cache);
  }
}

commit

@Override
public void commit(boolean required) throws SQLException {
  // 首先使用被包裹的Executor来进行提交
  delegate.commit(required);
  // 然后执行TransactionalCacheManager的提交
  tcm.commit();
}

缓存对象

那么上面使用到的MappedStatement的缓存对象是从哪里来的呢
这里可以看下MappedStatement的解析过程

private void configurationElement(XNode context) {
  try {
    // 省略
    cacheRefElement(context.evalNode("cache-ref"));
    cacheElement(context.evalNode("cache"));
    // 省略
  );
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

在解析Mapper时,会解析cache-ref和cache
cache-ref配置了当前命名空间使用哪个命名空间的缓存对象
cache-ref的解析过程也比较简单,就是在当前Mapper的解析辅助类中设置了引用的cache所属的命名空间

private void cacheRefElement(XNode context) {
  if (context != null) {
    configuration.addCacheRef(builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));
    CacheRefResolver cacheRefResolver = new CacheRefResolver(builderAssistant, context.getStringAttribute("namespace"));
    try {
      cacheRefResolver.resolveCacheRef();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteCacheRef(cacheRefResolver);
    }
  }
}
public Cache resolveCacheRef() {
  return assistant.useCacheRef(cacheRefNamespace);
}

cache则是创建属于自己的命名空间

private void cacheElement(XNode context) {
  if (context != null) {
    // 缓存对象的类型,默认是PERPETUAL
    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);
  }
}

TransactionalCacheManager

从上面CachingExecutor的query,update,commit等操作可以看出,并没有直接操作Cache对象,而是通过TransactionalCacheManager来操作cache
TransactionalCacheManager的主要作用就是为缓存添加了事务管理,当通过putObject来添加缓存时,此时缓存并不会直接添加到底层的缓存对象中,并且通过getObject来查询这个新添加的缓存也是查不到的,只有commit了之后,才会将添加的缓存真正地添加到底层的缓存对象中

重要属性
// key是要操作的缓存对象,value是TransactionalCache
private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();
getObject
public Object getObject(Cache cache, CacheKey key) {
  return getTransactionalCache(cache).getObject(key);
}
private TransactionalCache getTransactionalCache(Cache cache) {
  // 如果当前cache对应的TransactionalCache不存在,就创建一个
  return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
}
putObject
public void putObject(Cache cache, CacheKey key, Object value) {
  // 也是操作cache对应的TransactionalCache 
  getTransactionalCache(cache).putObject(key, value);
}
commit
public void commit() {
  // 遍历每个Cache对应的TransactionalCache进行提交
  for (TransactionalCache txCache : transactionalCaches.values()) {
    txCache.commit();
  }
}

TransactionalCache

从上面的代码可以看出,当通过TransactionalCacheManager来操作缓存对象时,都会将请求交给TransactionalCache
所以有关二级缓存的操作逻辑都集中在TransactionalCache

重要属性
// 实际的缓存对象
private final Cache delegate;
// 是否在commit的时候清空
private boolean clearOnCommit;
// key是缓存key,value是对应需要添加的缓存
private final Map<Object, Object> entriesToAddOnCommit;
// 没有命中缓存的key的集合
private final Set<Object> entriesMissedInCache;
getObject
public Object getObject(Object key) {
  // issue #116
  // 读取缓存
  Object object = delegate.getObject(key);
  // 没有命中缓存
  if (object == null) {
    // 记录没有命中缓存的key
    entriesMissedInCache.add(key);
  }
  // issue #146
  // 判断标志位,当调用clear方法后,会将这个标志位设置为true,此时即使底层的缓存对象中存在对应的缓存,仍然会返回Null
  // 当调用commit方法后,会重新将这个标志位设置为false
  if (clearOnCommit) {
    return null;
  } else {
    return object;
  }
}
putObject
@Override
public void putObject(Object key, Object object) {
  // 这里需要注意,并没有直接操作底层的缓存对象
  entriesToAddOnCommit.put(key, object);
}
commit
public void commit() {
  // 判断之前是否调用了clear,如果调用了会将底层的缓存清空
  if (clearOnCommit) {
    delegate.clear();
  }
  // 这里才会真正地将通过putObject添加的缓存添加到底层的缓存对象中
  flushPendingEntries();
  // 将clearOnCommit设置为false,并且将entriesToAddOnCommit和entriesMissedInCache清空
  reset();
}
flushPendingEntries
private void flushPendingEntries() {
  // 遍历通过putObject添加的缓存
  for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
    // 真正地添加到底层的缓存对象中
    delegate.putObject(entry.getKey(), entry.getValue());
  }
  // 将entriesMissedInCache中不包含的缓存项添加到二级缓存中
  for (Object entry : entriesMissedInCache) {
    if (!entriesToAddOnCommit.containsKey(entry)) {
      delegate.putObject(entry, null);
    }
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值