【MyBatis源码】CacheKey缓存键的原理分析

文章目录

Mybatis缓存设计

MyBatis 每秒过滤众多数据库查询操作,这对 MyBatis 缓存键的设计提出了很高的要求。MyBatis缓存键要满足以下几点。

无碰撞:必须保证两条不同的查询请求生成的键不一致,这是最重要也是必须满足的要求。否则会引发查询操作命中错误的缓存,并返回错误的结果。

高效比较:每次缓存查询操作都可能会引发键之间的多次比较,因此该操作必须是高效的。

高效生成:每次缓存查询和写入操作前都需要生成缓存的键,因此该操作也必须是高效的。

在编程中,我们常使用数值、字符串等简单类型作为键,然而,这类键容易产生碰撞。为了防止碰撞的发生,需要将键的生成机制设计得非常复杂,这又降低了键的比较效率和生成效率。因此,准确度和效率之间往往是相互制约的。

为了解决以上问题,MyBatis设计了一个 CacheKey类作为缓存键。整个 CacheKey设计得并不复杂,但又非常精巧。

设计图解释:

【1】 Mybatis缓存的使用和我们一般使用缓存方式相同,使用一个内存缓存(map)作为本地容器。对于查询请求优先查询本地缓存,如果有直接返回,没有查询数据库,并将数据库查询结果写入到缓存中。

【2】 Mybatis使用CacheKey类作为缓存(map)的key,重写了其hashcode和equal方法。

【3】 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); Mybatis主要根据mapper信息,参数值,分页信息,SQL信息设计缓存key

缓存KEY的设计

CacheKey类主体

java 复制代码
public class CacheKey implements Cloneable, Serializable {
   /**
   * 乘数,用来计算hashcode时使用
   */
  private final int multiplier;

  /**
   * 哈希值,整个CacheKey的哈希值
   */
  private int hashcode;

  /**
   * 求和校验码
   */
  private long checksum;

  /**
   * 更新次数,整个CacheKey的更新次数
   */
  private int count;
  /**
   * 更新历史
   */
  private List<Object> updateList;

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

  public void updateAll(Object[] objects) {
    for (Object o : objects) {
      update(o);
    }
  }

  @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) {
      return false;
    }
    if (checksum != cacheKey.checksum) {
      return false;
    }
    if (count != cacheKey.count) {
      return false;
    }

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

CacheKey组成

org.apache.ibatis.executor.BaseExecutor#createCacheKey

java 复制代码
  // 创建CacheKey对象
    CacheKey cacheKey = new CacheKey();
    // mapper-id
    cacheKey.update(ms.getId());
    // 分页参数
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    // 执行的SQL(带有占位符的)
    cacheKey.update(boundSql.getSql());
    // 执行SQL参数value值
    cacheKey.update(value);
    // 环境配置id
    cacheKey.update(configuration.getEnvironment().getId());

CacheKey主要由5部分组成:

【1】 mapper接口对应的statementId

【2】 分页参数

【3】 执行SQL

【4】 传入参数的值

【5】 当前环境的ID

CacheKey如何保证缓存key的唯一性

CacheKey首先类设计了多个重要属性,这些属性为结合传入的参数信息进行组合计算以提高缓存key的唯一性,并能够以较高的性能进行比较计算。

其中hashcode的计算主要通过update方法进行计算

java 复制代码
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对象是否相等时,会先进行类型判断,然后进行 hashcode、checksum、count的比较,只要有一项不相同则表明两个对象不同。以上操作都比较简单,能在很短的时间内完成。如果上面的各项属性完全一致,则会详细比较两个CacheKey 对象的变动历史 updateList,这一步操作相对复杂,但是能保证绝对不会出现碰撞问题。

【CacheKey生成的结果示例】

2042432675:5771996351:user.selectById:0:2147483647:select * from t_user where id = ? and name = ?:1:null:development

MyBatis 还准备了一个 NullCacheKey,该类用来充当一个空键使用。在缓存查询中,如果发现某个 CacheKey信息不全,则会返回 NullCacheKey对象,类似于返回一个null值。但是 NullCacheKey毕竟是 CacheKey的子类,在接下来的处理中不会引发空指针异常。这种设计方式也非常值得我们借鉴。

MyBatis生成的 CacheKey 对象中包含了这次查询的所有信息,包括查询语句的 id、查询的翻页限制、数据总量、完整的 SQL语句,这些信息一致就保证了两次查询的一致。结合 CacheKey的 equals方法,我们知道只要通过 equals方法判断两个CacheKey对象相等,则两次查询操作的条件必定是完全一致的。

相关推荐
莫寒清10 天前
Mybatis的插件原理
面试·mybatis
莫寒清10 天前
MyBatis 中动态 SQL 的作用
面试·mybatis
吹晚风吧10 天前
实现一个mybatis插件,方便在开发中清楚的看出sql的执行及执行耗时
java·sql·mybatis
码云数智-大飞10 天前
像写 SQL 一样搜索:dbVisitor 如何用 MyBatis 范式颠覆 ElasticSearch 开发
sql·elasticsearch·mybatis
Mr__Miss11 天前
mybatisPlus分页组件3.5.15版本报错解决方案
mybatis
无名-CODING11 天前
MyBatis中#{}和${}完全指南:从原理到实战
mybatis
鹿角片ljp11 天前
短信登录:基于 Session 实现(黑马点评实战)
java·服务器·spring boot·mybatis
莫寒清11 天前
MyBatis 如何防止 SQL 注入?
面试·mybatis
玄〤11 天前
个人博客网站搭建day5--MyBatis-Plus核心配置与自动填充机制详解(漫画解析)
java·后端·spring·mybatis·springboot·mybatis plus
计算机学姐11 天前
基于SpringBoot的服装购物商城销售系统【协同过滤推荐算法+数据可视化统计】
java·vue.js·spring boot·mysql·信息可视化·mybatis·推荐算法