Mybatis源码3 CachingExecutor, 二级缓存,缓存的实现

Mybatis CachingExecutor, 二级缓存,缓存的实现

一丶二级缓存概述

Mybatis源码3 CachingExecutor, 二级缓存,缓存的实现

上一章节,我们知道mybaits在构造SqlSession的时候,需要让SqlSession持有一个执行器,如果配置了缓存开启,那么在Configuration.newExecutor的时候,会使用CachingExecutor对执行器进行装饰(被装饰可能是SimpleExecutor,ReuseExecutor,BatchExecutor)

  //如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
  if (cacheEnabled) {
  //上面根据配置的ExecutorType 生成了executor==> SimpleExecutor or ReuseExecutor or BatchExecutor
      //这里的开启不意味着一定开启二级缓存,而是说通过CachingExecutor装饰,可能用到二级缓存,为什么这么说,见  CachingExecutor 代码学习
    executor = new CachingExecutor(executor);
  }

二丶为什么需要二级缓存

一级缓存是SqlSession级别的(SqlSession持有一个Excutor,一个Excutor对于一个一级缓存)

所以SqlSesion的生命周期等于一级缓存的生命周期,且不同SqlSession的一级缓存不共用

不同session进行相同SQL查询的时候,是查询两次数据库的。显然这是一种浪费,既然SQL查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是MyBatis二级缓存的初衷。如果开启二级缓存,关闭sqlsession后,会把该sqlsession一级缓存中的数据添加到mapper namespace的二级缓存中。这样,缓存在sqlsession关闭之后依然存在。

三丶Cache的实现类和缓存管理器

Mybatis源码3 CachingExecutor, 二级缓存,缓存的实现

其实还有一个BlockingCache,但是我的idea画图有点问题

  • PerpetualCache mybatis 提供的缓存默认实现,基于hashMap
  • TransactionalCache,事务缓存,持有以下字段
//装饰器模式,真正的缓存操作将交由此执行
private Cache delegate;
//是否因为commit而清空了缓存 避免脏读
private boolean clearOnCommit;
//commit时要添加的元素  二级缓存变更的数据都将存储在 entriesToAddOnCommit中,后续同步到真正的缓存空间 //有点类似于git的本地代码 commit之后经过push才提交到云端服务器,只不过mybatis的push是commit操作
private Map entriesToAddOnCommit;
//缓存未命中的key 避免缓存穿透
private Set entriesMissedInCache;
  • TransactionalCacheManager 事务缓存管理器,没有实现cache接口,持有以下字段
//管理了许多TransactionalCache
//二级缓存是和nameSpace相关的,map的key对应一个mapperStatement
// map的value 是上面讲到的事务缓存,事务缓存持有 Cache delegate指向真正的二级缓存数据存储区域
  private Map transactionalCaches = new HashMap();

每一个Sqlsession对应一个CachingExecutor,每一个CachingExcutor对应一个事务缓存管理器,每一个事务缓存管理器有多个value,value中的TransactionalCache 中的delegate是真正的二级缓存缓存区,entriesToAddOnCommit是其暂存区

Mybatis源码3 CachingExecutor, 二级缓存,缓存的实现

四丶CachingExcutor源码学习

1.查询

public  List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
    throws SQLException {
    //二级缓存适合MappedStatement 绑定的 MappedStatement是和mapper的nameSpace一对一的
    //这里的cache是属于这个mapper的暂存区
  Cache cache = ms.getCache();
  //默认情况下是没有开启缓存的(二级缓存).要开启二级缓存,
  // 你需要在你的 SQL 映射文件中添加一行:  or使用注解
  //先查CacheKey,查不到再委托给实际的执行器去查
  if (cache != null) {
      //如果需要刷新缓存 对应flushCache的配置
    flushCacheIfRequired(ms);
      // 如果配置了使用 二级缓存 useCach配置
      //
    if (ms.isUseCache() && resultHandler == null) {
        //确定是存储过程的时候没有out类型的参数 如果有抛出异常
        //二级缓存不支持有输出的存储过程
      ensureNoOutParams(ms, parameterObject, boundSql);
        //根据暂存区的cache从缓存区取值
      List list = (List) tcm.getObject(cache, key);
      if (list == null) {
          //没有那么调用被装饰着去查询 还会走到BaseExecutor的一级缓存
        list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //存到暂存区
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }
    //没有使用二级缓存 那么被装饰者执行
  return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

为什么说是提交到暂存区了

//TransactionalCacheManager的
  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }
// TransactionalCache 的 putObject
//其实是加到了 TransactionalCache的entriesToAddOnCommit中了 等commit的时候再加入到二级缓存,从而保证二级缓存中的数据是提交后的避免造成脏读
//假设发生了一个写操作,执行完成后另外一个请求查询到了该数据直接放置到二级缓存区域,但是此时这条数据执行了回滚操作,那么此时就会造成一个脏读!
@Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

2.更新

 public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//刷新缓存完再update
     //这里的刷新缓存并不是立马将二级缓存中的数据进行清空
     //而是TransactionalCache中的clearOnCommit 置为了true
//如果更新的时候直接清空二级缓存 那么发生回滚的时候,二级缓存的数据回不来了,降低了二级缓存的命中率
   flushCacheIfRequired(ms);
   return delegate.update(ms, parameterObject);
 }

3.commit

@Override
public void  (boolean required) throws SQLException {
  delegate.commit(required);
  tcm.commit();
}

 //tcm.commit(); 多了commit方法,提供事务功能
  public void commit() {
      //提交是先清空缓存,避免被缓存和数据库不一致
    if (clearOnCommit) {
      delegate.clear();
    }
      //把待提交的数据 加入到二级缓存
    flushPendingEntries();
      //重置属性  clearOnCommit = false;entriesToAddOnCommit清空,entriesMissedInCache清空
    reset();
  }

  private void flushPendingEntries() {
      //待提交的数据 加入到二级缓存
    for (Map.Entry entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
      //没有命中的数据赋值为null  "这样可以避免缓存穿透么?"
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

4.rollback

  @Override
  public void rollback(boolean required) throws SQLException {
    try {
      delegate.rollback(required);
    } finally {
      if (required) {
        tcm.rollback();
      }
    }
  }

//tcm.rollback
public void rollback() {
  unlockMissedEntries();
  reset();
}
private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      delegate.putObject(entry, null);
    }
  }

五丶Mybatis是如何把众多Cache的装饰器串联起来组成责任链模式的

mybatis构建一个二级缓存使用的是CacheBuilder(建造者模式)

//对应mapperStateMent的id
private String id;
//二级缓存的具体实现 也就是说可以通过配置进行自定义的二级缓存,甚至是基于第三方如redis的缓存
  private Class implementation;
//装饰器,通过配置 可以选择使用那些进行装饰增强
private List> decorators;
//缓存的大小
private Integer size;
//缓存自动清楚间隔
private Long clearInterval;
//读写锁
  private boolean readWrite;
//是否阻塞
private boolean blocking;
public Cache build() {
    //如果用户没有自定义的缓存 那么使用PerpetualCache 如果用户没有指定的淘汰策略 那么使用LruCache 最近最少使用的原则进行淘汰
  setDefaultImplementations();
  //先new一个base的cache(PerpetualCache)  没有指定 那么就是反射生成PerpetualCache
  Cache cache = newBaseCacheInstance(implementation, id);
  //设额外属性
  setCacheProperties(cache);
  // issue #352, do not apply decorators to custom caches
    //装饰器 只对默认的二级缓存PerpetualCache 生效,也就是说自定义的cache不被装饰,淘汰策略,锁等等要自己实现
  if (PerpetualCache.class.equals(cache.getClass())) {
    for (Class decorator : decorators) {
        //装饰者模式一个个包装cache 反射构造函数 把被装饰者作为入参
      cache = newCacheDecoratorInstance(decorator, cache);
      //又要来一遍设额外属性
      setCacheProperties(cache);
    }
    //最后附加上标准的装饰者
    cache = setStandardDecorators(cache);
  } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      //如果是custom缓存,且没有日志装饰,要加日志
    cache = new LoggingCache(cache);
  }
  return cache;
}
//最后附加上标准的装饰者
private Cache setStandardDecorators(Cache cache) {
  try {
    MetaObject metaCache = SystemMetaObject.forObject(cache);
    if (size != null && metaCache.hasSetter("size")) {
        //反射设置缓存大小
      metaCache.setValue("size", size);
    }
    if (clearInterval != null) {
      //刷新缓存间隔,怎么刷新呢,用ScheduledCache来刷,还是装饰者模式
      cache = new ScheduledCache(cache);
      ((ScheduledCache) cache).setClearInterval(clearInterval);
    }
    if (readWrite) {
        //如果readOnly=false,可读写的缓存 会返回缓存对象的拷贝(通过序列化) 。这会慢一些,但是安全,因此默认是 false。
      cache = new SerializedCache(cache);
    }
    //日志缓存
    cache = new LoggingCache(cache);
    //同步缓存
    cache = new SynchronizedCache(cache);
    if (blocking) {
      cache = new BlockingCache(cache);
    }
    return cache;
  } catch (Exception e) {
    throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
  }
}

Original: https://www.cnblogs.com/cuzzz/p/16609744.html
Author: Cuzzz
Title: Mybatis源码3 CachingExecutor, 二级缓存,缓存的实现

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/713899/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 腾讯PHP/GO工程师面试经历

    一面是技术面,用的腾讯会议,开局自我介绍之后就开始做题。题目不算难,都非常考验基础扎不扎实。面试官特别喜欢就一个问题深入去问,直到你卡壳。 第一题是非常经典的,从浏览器敲下地址到页…

    技术杂谈 2023年5月31日
    098
  • 【软考】软件测试

    1.重要的概念 测试用例应包括名称和标识、测试追踪、用例说明、测试的初始化要求,测试的输入、期望的测试结果、评价测试结果的准则操作过程,前提条件和约束、测试终止条件。 软件测试的方…

    技术杂谈 2023年5月31日
    096
  • 23种设计模式之迭代器模式

    文章目录 概述 迭代器模式的优缺点 迭代器模式的结构和实现 * 模式结构 模式实现 总结 ; 概述 迭代器模式就是顺序访问聚集中的对象,一般来说,集合中非常常见,如果对集合类比较熟…

    技术杂谈 2023年7月24日
    097
  • cocos 场景制作流程

    前面的话 本文将详细介绍 cocos 场景制作流程 节点和组件 Cocos Creator 的工作流程是以组件式开发为核心的,组件式架构也称作组件-实体系统,简单的说,就是以组合而…

    技术杂谈 2023年5月30日
    091
  • 数学基础之概率

    本文主要介绍概率与数理统计中的一些常见的基本概念。 对于随机试验,尽管在每次试验之前不能预知试验的结果,但是试验的所有可能结果集合是已知的,我们将随机试验E的所有可能的结果组成的集…

    技术杂谈 2023年5月31日
    078
  • OKR、KPI、360环评

    OKR、KPI、360环评,我是如何被绩效一步步内卷的 – 知乎https://zhuanlan.zhihu.com/p/358901842 OKR和360度环评 &#…

    技术杂谈 2023年6月1日
    092
  • Python 集合相关知识

    交集:(& 或者 intersection) set1 = {1, 2, 4, 5} set2 = {4, 5, 7, 8} print(set1 & set2) …

    技术杂谈 2023年6月21日
    088
  • 编译器LLVM-MLIR-Intrinics-llvm backend-instruction

    编译器LLVM-MLIR-Intrinics-llvm backend-instruction 参考文献链接 https://mp.weixin.qq.com/s/G36IllLO…

    技术杂谈 2023年5月31日
    081
  • Git rebase 合并多次提交

    在一般研发管理流程中,我们一般都是这么使用Git版本的: 0、先拿到一个需求(不细谈需求前面的采集、归纳整理、确认及评审等环节) 1、从主分支checkout一个新分支 2、在完成…

    技术杂谈 2023年7月11日
    086
  • Go测试技术分享(一):场景化接口Case编写

    一、前言 本人负责的支付清结算方向的测试工作,在测试项目中,会出现流程化的接口调用,请求完一个接口后,继续请求另一个接口(这里的接口可以指Http,也指rpc接口),这里以一个真实…

    技术杂谈 2023年5月31日
    085
  • R12.2.7 开启预置用户账号

    [root@apps scripts]# cd /u01/install/APPS/scripts [root@apps scripts]# ll total 244 -rwxr-…

    技术杂谈 2023年6月1日
    084
  • Python——静态方法、类方法、公有方法、私有方法

    普通实例方法,第一个参数需要是self,它表示一个具体的实例本身。 静态方法是类中不需要实例的函数,无self,仅仅是类中的函数。可以由类实例或类调用。(1)使用staticmet…

    技术杂谈 2023年7月11日
    073
  • 【Python-基础】基础是一切升华的根本

    以下仅做相关知识的简述,更深入的了解和学习,请自行查阅资料或留言。 Python是一种编程语言,可以让您更快地工作,并更有效地集成您的系统。Python is a programm…

    技术杂谈 2023年7月24日
    0125
  • DHCP:IP 并非与生俱来

    初识 DHCP 众所周知,因特网上的每台设备都规定了其全世界唯一的地址,也就是说 “IP 地址”,正是由于有了 IP 地址,才保证了用户在连网的计算机上操作…

    技术杂谈 2023年7月24日
    062
  • synchronized原理剖析

    synchronized原理剖析 并发编程存在什么问题? 1️⃣ 可见性 可见性:是指当一个线程对共享变量进行了修改,那么另外的线程可以立即看到修改后的最新值。 案例演示:一个线程…

    技术杂谈 2023年6月21日
    095
  • 技术管理者的困惑——技术与管理应该如何平衡?

    原创不易,求分享、求一键三连 前段时间有个粉丝与我讨论了一个问题: 小钗,我半年前从技术经理升职到了技术总监,但这段时间的工作很恼火:一大半时间要去开各种产品会,还有一些时间要去处…

    技术杂谈 2023年6月1日
    068
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球