Mybatis源码解析(六):缓存执行器操作流程

Mybatis源码系列文章

手写源码(了解源码整体流程及重要组件)

Mybatis源码解析(一):环境搭建

Mybatis源码解析(二):全局配置文件的解析

Mybatis源码解析(三):映射配置文件的解析

Mybatis源码解析(四):sql语句及#{}、${}的解析

Mybatis源码解析(五):SqlSession会话的创建

Mybatis源码解析(六):缓存执行器操作流程

Mybatis源码解析(七):查询数据库主流程

Mybatis源码解析(八):Mapper代理原理

Mybatis源码解析(九):插件机制

Mybatis源码解析(十):一级缓存和二级缓存



前言

  • 之前篇章讲了配置文件的解析与SqlSession的创建,可以说都是在为执行增删改查操作主流程做铺垫
  • 接下来让我们进入SqlSession的selectOne实现方法

在这里插入图片描述


一、会话对象selectOne方法

selectOne方法

  • SqlSession接口有两个实现类:
  • 方法入参:
    • statement:statementId = “namespace.id”
    • parameter:方法参数,填充带?的sql
  • this.selectList:调用同类的方法,参数传递,从这里可以看出,selectOne方法的实现都交给了selectList,获取到只取第一个值
    • 只有一个值,获取返回即可
    • 如果是空,则返回空
    • 如果大于一个,则报错,因为此方法目的是查询唯一的一个值,结果多个
@Override
public <T> T selectOne(String statement, Object parameter) {
  // Popular vote was to return null on 0 results and throw exception on too many.
  List<T> list = this.selectList(statement, parameter);
  if (list.size() == 1) {
    return list.get(0);
  } else if (list.size() > 1) {
    throw new TooManyResultsException("Expected one result (or null) 
    to be returned by selectOne(), but found: " + list.size());
  } else {
    return null;
  }
}

selectList方法

  • 三个selectList方法依次向下调用
  • 第一个方法添加默认分页对象调用第二个方法
  • 第二个方法添加结果集处理器空对象调用第三个方法
  • 第三个核心方法:
    • 通过statementId获取MappedStatement(由每个<select><insert><update><delete>内属性构建而成的对象)
    • executor:默认无配置情况下,是简单执行器外面包一层缓存执行器。具体这里又讲:Mybatis源码解析(五):SqlSession会话的创建
// 第一个
@Override
public <E> List<E> selectList(String statement, Object parameter) {
  // 调用重载方法
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

// 第二个
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  // 参数4:结果集处理器
  return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}

// 第三个
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
  try {
    // 根据传入的statementId,获取MappedStatement对象
    MappedStatement ms = configuration.getMappedStatement(statement);
    // 调用执行器的查询方法
    // wrapCollection(parameter)是用来装饰集合或者数组参数
    return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
  } catch (Exception e) {
    throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
  } finally {
    ErrorContext.instance().reset();
  }
}

二、获取缓存对象key

1. 从MappedStatement对象中获取BoundSql对象

2. 获取一级或二级缓存Map的key,value是查询的结果

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  // 获取绑定的SQL语句,比如 "SELECT * FROM user WHERE id = ? "
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  // 生成缓存Key
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

1、获取BoundSql对象

public BoundSql getBoundSql(Object parameterObject) {
  BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings == null || parameterMappings.isEmpty()) {
    boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
  }

  //  
  for (ParameterMapping pm : boundSql.getParameterMappings()) {
    String rmId = pm.getResultMapId();
    if (rmId != null) {
      ResultMap rm = configuration.getResultMap(rmId);
      if (rm != null) {
        hasNestedResultMaps |= rm.hasNestedResultMaps();
      }
    }
  }

  return boundSql;
}

进入getBoundSql

  • 这里只展示非动态sql(#{}样式),动态的sql需要动态组装sql,而非动态带?的sql已经准备好了
  • BoundSql的组成:
    • 带?的sql
    • #{}或${}中的属性参数
    • 入参属性值
@Override
public BoundSql getBoundSql(Object parameterObject) {
  return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}

2、获取缓存的key值

进入CachingExecutor的createCacheKey方法

  • delegate:简单执行器、批量执行器、重用执行器其中的一种,这里使用他们的父类Executor接受
  • Mybatis源码解析(五):SqlSession会话的创建-默认情况下,缓存开启,则以上的三种执行器会在外面包一层(创建一个缓存执行器,构造函数传入以上执行器,jdbc操作由他们处理,缓存操作由缓存执行器处理)
  • 由于创建key是不同执行器的公共操作,则方法在他们三的抽象父类BaseExecutor中
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}

进入BaseExecutor的createCacheKey方法

  1. 创建CachKey对象(一二级缓存都用这个当key)
  2. update方法更新
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
  if (closed) {
    throw new ExecutorException("Executor was closed.");
  }
  // 创建cacheKey对象
  CacheKey cacheKey = new CacheKey();
  // id
  cacheKey.update(ms.getId());
  // 分页参数
  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
    // 当前环境的值也会设置
    cacheKey.update(configuration.getEnvironment().getId());
  }
  return cacheKey;
}

1)CacheKey构造方法

  • 初始化一些字段值,为之后重写hashCode和equals方法做准备
...
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;

// 参与hash运算的乘数
private final int multiplier; 
// cachekey的hash值,在update函数中实时算出来
private int hashcode; 
// 校验和,hash值的和
private long checksum; 
// updateList的中的元素个数
private int count; 
// 根据该集合中的元素判断两个cacheKey是否相同
private List<Object> updateList;

public CacheKey() {
  this.hashcode = DEFAULT_HASHCODE;
  this.multiplier = DEFAULT_MULTIPLIER;
  this.count = 0;
  this.updateList = new ArrayList<>();
}
...

2)更新方法

  • 通过入参计算hashcode值,将入参添加到更新集合
  • 入参:statementId、分页参数、带?的sql、参数值、环境id
  • 比较CacheKey对象就是比较上述参数的hashcode值,相同则返回以前存储的value,不相同则查询数据库
public void update(Object object) {
  // 获取参数的object的hash值
  int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);

  // 更新count 、checksum以及hashcode的值
  count++;
  checksum += baseHashCode;
  baseHashCode *= count;
  hashcode = multiplier * hashcode + baseHashCode;

  // 将对象添加到list集合中
  updateList.add(object);
}

equals和hashCode方法

@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) { // hashcode是否相同
    return false;
  }
  if (checksum != cacheKey.checksum) { // checksum是否相同
    return false;
  }
  if (count != cacheKey.count) { // count是否相同
    return false;
  }

  // 按顺序比较updateList中元素的hash值是否相同
  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;
}

@Override
public int hashCode() {
  return hashcode;
}

三、二级缓存的流程

  • 回到第二章节的开头;获取完缓存的CachKey对象后,进入缓存执行器重载方法query
  • ms.getCache():namespace启动二级标签;MappedStatement对象中属性,所以在映射配置文件中配置,如下
    在这里插入图片描述
  • flushCacheIfRequired:<select>标签中的flushCache属性,刷新二级缓存,默认false
  • ms.isUseCache:<select>标签中的useCache属性,此标签启用二级缓存,默认true
  • 先从二级缓存tcm中获取,没有则委托BaseExecutor去查询一级缓存或数据库
  • 再讲结果放入二级缓存tcm,其实只是放入一个map(非二级缓存的map),因为只有提交事务才会真正添加到二级缓存(二级缓存的get和put后面篇章单独讲)
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
  // 获取二级缓存
  Cache cache = ms.getCache();
  if (cache != null) {
    // 刷新二级缓存 (存在缓存且flushCache为true时)
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      // 从二级缓存中查询数据
      List<E> list = (List<E>) tcm.getObject(cache, key);
      // 如果二级缓存中没有查询到数据,则查询一级缓存及数据库
      if (list == null) {
        // 委托给BaseExecutor执行
        list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        // 将查询结果 要存到二级缓存中(注意:此处只是存到map集合中,没有真正存到二级缓存中)
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
  // 委托给BaseExecutor执行
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

四、一级缓存的流程

  • 第三章的delegate.query方法,delegate此时为简单执行器SimpleExecutor,而query方法在他的父类BaseExecutor
    在这里插入图片描述
  • queryStack为0:表示是当前会话只有本次查询而没有其他的查询了
  • ms.isFlushCacheRequired():与二级缓存中的<select>标签中的flushCache属性功能一样
  • 先从一级缓存localCache中获取,没有则从数据库查询
@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.");
  }
  // 1. 如果配置了flushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存
  if (queryStack == 0 && ms.isFlushCacheRequired()) {
    // 1.1. 清空缓存
    clearLocalCache();
  }
  List<E> list;
  try {
    // 2. 查询堆栈 + 1
    queryStack++;
    // 从一级缓存中获取数据
    list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
    if (list != null) {
      // 3.1. 已有缓存结果,则处理本地缓存结果输出参数(存储过程)
      handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
    } else {
      // 3.2. 没有缓存结果,则从数据库查询结果
      list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }
  } finally {
    // 查询堆栈数 -1
    queryStack--;
  }
  if (queryStack == 0) {
    for (DeferredLoad deferredLoad : deferredLoads) {
      deferredLoad.load();
    }
    // issue #601
    deferredLoads.clear();
    if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
      // issue #482
      clearLocalCache();
    }
  }
  return list;
}

进入queryFromDatabase方法

  • 因为延迟加载的原因,这里一级缓存先添加一个占位符
  • 查询结果以后,移除然后再put
  • 具体的查询数据库操作doQuery方法,下一篇文章专门来讲
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
  List<E> list;
  // 1. 首先向本地缓存中存入一个ExecutionPlaceholder的枚举类占位value
  localCache.putObject(key, EXECUTION_PLACEHOLDER);
  try {
    // 2. 执行doQuery方法
    list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
  } finally {
    // 3. 执行完成移除这个key
    localCache.removeObject(key);
  }
  // 4. 查询结果存入缓存中
  localCache.putObject(key, list);
  // 5. 如果MappedStatement的类型为CALLABLE,则向localOutputParameterCache缓存中存入value为parameter的缓存
  if (ms.getStatementType() == StatementType.CALLABLE) {
    localOutputParameterCache.putObject(key, parameter);
  }
  return list;
}

总结

  • 一级缓存和二级缓存就是一个Map集合对象:
    • key:Cache对象(statementId、分页参数、带?的sql、参数值、环境id)
    • value:数据库查询结果
  • 一级缓存默认开启,二级缓存需要添加 <cache>标签开启
  • 都开启情况下:先从二级缓存获取,没有则从一级缓存获取,还没有则查询数据库(查询结果后先添加到一级缓存,再添加到二级缓存)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冬天vs不冷

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值