美文网首页
Cache接口的设计以及CacheKey的定义

Cache接口的设计以及CacheKey的定义

作者: 云芈山人 | 来源:发表于2021-06-27 07:21 被阅读0次

Mybatis 定义了org.apache.ibatis.cache.Cache接口作为Cache的SPI(Service Provider Interface),Mybatis所有的内部缓存都需要去实现这个接口。MyBatis定义了PerpetualCache实现类实现了Cache接口。事实上,为SqlSession对象创建的Executor内部维护的Cache类型实例对象就是PerpetualCache子类创建的。

  • 一级缓存只涉及到PerpetualCache子类。
  • Cache的其它实现如下图:


    Cache装饰器.png

如何确定Cache中key?

Cache最核心的实现就是HashMap,将查询使用的特征值作为key,将查询到的结果作为value,存储到HashMap中。

  • 问题
    如何确定Cache中key?也就是怎样来确定此次查询的特征值,也可以说是怎么样判断两次查询是完全相同的查询?

  • 分析
    要确定两次查询是同一个查询,就需要满足以上四种特征:

    1. 传入的statementId
      对于MyBatis来说,要使用它,必须要有一个statementId,它代表着你会执行怎样的Sql。
    2. 查询中要求查询结果集中的结果范围
      MyBatis自身的分页通过RowBounds来实现,它通过rowBounds.offset和rowBounds.limit来过滤查询出来的结果集。这种分页功能是查询结果的再过滤,并非是数据库的物理分页。
    3. 这次查询所产生的最终要传递给JDBC java.sql.Preparedstatement的sql语句字符串(boundSql.getSql() )
    4. 传递给java.sql.Statement要设置的参数值
      第3和第4条的本质要求就是:调用JDBC时,传入的SQL语句要一模一样,传递给JDBC的参数值也要一模一样。
  • 结论
    综上可得出,CacheKey的组合应如下:
    statementId + rowBounds + 传递给JDBC的sql + 传递给JDBC的参数

  • CacheKey的创建
    对于每次的查询请求,Executor会根据传递的参数信息以及动态生产的SQL语句,按上述条件根据一定的计算规则,创建对应的CacheKey对象。
    1 目的
    CacheKey作为key去缓存Cache中查找缓存结果;
    如果查找缓存命中失败,可以此CacheKey为key,数据库查询的结果为value,组成键值对,存入Cache缓存中。

    1. 代码
      构建放在了Executor接口的实现类BaseExecutor中:
@Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    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();
    // 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;
  }
  • CacheKey的hashcode生成算法
    Cache接口的实现,本质是使用HashMap<k,v>,CacheKey作为key值,HashMap通过key值的hashcode来组织和存储,也即是构建CacheKey实际上就是构造其hashCode的过程。

CacheKey的核心hashcode生成算法

public void update(Object object) {
    if (object != null && object.getClass().isArray()) {
      int length = Array.getLength(object);
      for (int i = 0; i < length; i++) {
        Object element = Array.get(object, i);
        doUpdate(element);
      }
    } else {
      doUpdate(object);
    }
  }
 
  private void doUpdate(Object object) {
    
    //1. 得到对象的hashcode;  
    int baseHashCode = object == null ? 1 : object.hashCode();
    //对象计数递增
    count++;
    checksum += baseHashCode;
    //2. 对象的hashcode 扩大count倍
    baseHashCode *= count;
    //3. hashCode * 拓展因子(默认37)+拓展扩大后的对象hashCode值
    hashcode = multiplier * hashcode + baseHashCode;
    updateList.add(object);
  }

相关文章

网友评论

      本文标题:Cache接口的设计以及CacheKey的定义

      本文链接:https://www.haomeiwen.com/subject/krapyltx.html