百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

mybatis3 源码深度解析-mybatis 缓存

bigegpt 2024-08-03 11:47 1 浏览

大纲

  1. mybatis 缓存的使用
  2. mybatis 缓存实现类
  3. mybatis 一级缓存实现原理
  4. mybatis 二级缓存实现原理

1 mybatis 缓存的使用

  • mybatis 一级缓存介绍

Mybatis缓存分为一级缓存和二级缓存,一级缓存默认开启而且无法关闭, 一级缓存只能控制缓存级别,不能关闭.


Mybatis 提供了一个配置参数 localCacheScope,用于控制一级缓存的级别.默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。

  • 一级缓存的生命周期有多长?

1 MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;

当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。

如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;

如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;

SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;

1 设置 cacheEnabled 为 true

2 在 Mapper.xml 中配置 <cache/> 即可

3 通过 useCache 属性指定执行 sql 时是否使用缓存;f使用lushCache属性清除缓存

<select ... flushCache="false" useCache="true"/>

<insert ... flushCache="true"/>

<update ... flushCache="true"/>

<delete ... flushCache="true"/>


基本上就是这样。<cache/>语句的效果如下:

映射语句文件中的所有 select 语句的结果将会被缓存。

映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。

缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。

缓存不会定时进行刷新(也就是说,没有刷新间隔)。

缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。

缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

二级缓存是用来解决一级缓存不能跨会话共享的问题的,范围是namespace 级别的,可以被多个SqlSession 共享(只要是同一个接口里面的相同方法,都可以共享),生命周期和应用同步。

如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:二级缓存 —> 一级缓存 —> 数据库。

实际上MyBatis 用了一个装饰器的类来维护,就是CachingExecutor。如果启用了二级缓存,MyBatis 在创建Executor 对象的时候会对Executor 进行装饰。

CachingExecutor 对于查询请求,会判断二级缓存是否有缓存结果,如果有就直接返回,如果没有委派交给真正的查询器Executor 实现类,比如SimpleExecutor 来执行查询,再走到一级缓存的流程。最后会把结果缓存起来,并且返回给用户。

参考文档: Mybatis缓存 - Lucky小黄人^_^ - 博客园

2 mybatis 缓存实现类

  • mybatis 通过 Cache 接口定义缓存对象的行为
/**
 *  SPI for cache providers.
 */
public interface Cache {
  //用于获取缓存 id,通常缓存 id 为 Mapper 的命名空间名称
  String getId();
  //将一个 java 对象添加到缓存中
  void putObject(Object key, Object value);
  //通过缓存 key 获取缓存的对象
  Object getObject(Object key);
  Object removeObject(Object key);
  //清空缓存
  void clear();
  int getSize();
}
  • Cache 接口 有一个基本实现类 PerpetualCache,通过HashMap 缓存对象
LoggingCache 为缓存增加日志输出功能 ,记录缓存请求和命中次数
LruCache 使用LRU 淘汰算法,淘汰最近使用最少的 缓存 key 和 value
ScheduledCache 如果当前时间与上次清空缓存的时间间隔大于指定时间间隔,则清空缓存
BlockingCache:能够保证 同一时间 只有一个线程 到缓存中 查找 指定的Key对应的数据
...

Cache cache = new PerpetualCache("default");
cache = new LruCache(cache);
cache = new FifoCache(cache);
cache = new SoftCache(cache);
cache = new WeakCache(cache);
cache = new ScheduledCache(cache);
cache = new SerializedCache(cache);
// cache = new LoggingCache(cache);
cache = new SynchronizedCache(cache);
cache = new TransactionalCache(cache);
for (int i = 0; i < 10000; i++) {
  cache.putObject(i, i);
  ((TransactionalCache) cache).commit();
  Object o = cache.getObject(i);
  assertTrue(o == null || i == ((Integer) o));
}
assertTrue(cache.getSize() < N);

// 另外,MyBatis提供了一个CacheBuilder类,通过生成器模式创建缓存对象
nitializingCache cache =CacheBuilder("test")
  .implementation(PerpetualCache.class)
	.addDecorator(LruCache.class)
  .build());


3 mybatis 一级缓存实现原理

  • 一级缓存的逻辑在 BaseExecutor 中完成,一级缓存通过PerpetualCache实现
public abstract class BaseExecutor implements Executor {
  //mybatis  一级缓存对象
  protected PerpetualCache localCache;
  //存储过程输出参数缓存
  protected PerpetualCache localOutputParameterCache;
  
  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.");
    }
    // 如果需要刷新缓存(默认DML需要刷新,也可以语句层面修改), 且queryStack=0 (应该是用于嵌套查询的场景)
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 如果查询不需要应用结果处理器,则先从缓存获取,这样可以避免数据库查询。我们后面会分析到localCache是什么时候被设置进去的
      //这里是一级缓存 默认开启的
      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();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
}

4 mybatis 二级缓存实现原理

  • mybatis 二级缓存默认是关闭的,通过设置 cacheEnabled 为 true 开启
  • Executor接口有多个实现: SimpleExecutor,ReuseExecutor,BatchExecutor 另外还有一个比较特殊的CachingExecutor ,使用装饰器模式,在其他 executor 基础上增加了二级缓存功能
  • Configuration类提供了一个工厂方法 newExecutor() 创建 executor 对象
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    //根据executorType创建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);
    }
    if (cacheEnabled) {
        // 如果启用了缓存,则使用CachingExecutor对executor进行装饰。
        executor = new CachingExecutor(executor);
    }

    // 责任链模式 executor 作为入参,为每个拦截器生成代理executor对象
    // 后面的代理executor,持有前面代理executor的引用,返回最终的代理executor
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}
  • CachingExecutor二级缓存查询实现逻辑
 @Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  //499085486:2272766813:cn.javabus.mapper.UserMapper.getUser:0:2147483647:select * from user where id = ?:1:development
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
  // 读取MappedStatement 对象中维护的二级缓存对象
  Cache cache = ms.getCache();
  if (cache != null) {
    //判断是否需要刷新缓存
    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);
         //将数据缓存到MappedStatement对象对应的二级缓存中
        tcm.putObject(cache, key, list); // issue #578 and #116
      }
      return list;
    }
  }

  //没有缓存数据,委托任务给 BaseExecutor对应实现类
  return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
  • 总结

1 缓存时 mybatis 中比较重要的特性

2 Mybatis缓存分为一级缓存和二级缓存,一级缓存默认开启而且无法关闭, 一级缓存只能控制缓存级别,不能关闭.

3 mybatis 二级缓存默认是关闭的,通过设置 cacheEnabled 为 true 开启,通过CachingExecutor实现二级缓存

相关推荐

得物可观测平台架构升级:基于GreptimeDB的全新监控体系实践

一、摘要在前端可观测分析场景中,需要实时观测并处理多地、多环境的运行情况,以保障Web应用和移动端的可用性与性能。传统方案往往依赖代理Agent→消息队列→流计算引擎→OLAP存储...

warm-flow新春版:网关直连和流程图重构

本期主要解决了网关直连和流程图重构,可以自此之后可支持各种复杂的网关混合、多网关直连使用。-新增Ruoyi-Vue-Plus优秀开源集成案例更新日志[feat]导入、导出和保存等新增json格式支持...

扣子空间体验报告

在数字化时代,智能工具的应用正不断拓展到我们工作和生活的各个角落。从任务规划到项目执行,再到任务管理,作者深入探讨了这款工具在不同场景下的表现和潜力。通过具体的应用实例,文章展示了扣子空间如何帮助用户...

spider-flow:开源的可视化方式定义爬虫方案

spider-flow简介spider-flow是一个爬虫平台,以可视化推拽方式定义爬取流程,无需代码即可实现一个爬虫服务。spider-flow特性支持css选择器、正则提取支持JSON/XML格式...

solon-flow 你好世界!

solon-flow是一个基础级的流处理引擎(可用于业务规则、决策处理、计算编排、流程审批等......)。提供有“开放式”驱动定制支持,像jdbc有mysql或pgsql等驱动,可...

新一代开源爬虫平台:SpiderFlow

SpiderFlow:新一代爬虫平台,以图形化方式定义爬虫流程,不写代码即可完成爬虫。-精选真开源,释放新价值。概览Spider-Flow是一个开源的、面向所有用户的Web端爬虫构建平台,它使用Ja...

通过 SQL 训练机器学习模型的引擎

关注薪资待遇的同学应该知道,机器学习相关的岗位工资普遍偏高啊。同时随着各种通用机器学习框架的出现,机器学习的门槛也在逐渐降低,训练一个简单的机器学习模型变得不那么难。但是不得不承认对于一些数据相关的工...

鼠须管输入法rime for Mac

鼠须管输入法forMac是一款十分新颖的跨平台输入法软件,全名是中州韵输入法引擎,鼠须管输入法mac版不仅仅是一个输入法,而是一个输入法算法框架。Rime的基础架构十分精良,一套算法支持了拼音、...

Go语言 1.20 版本正式发布:新版详细介绍

Go1.20简介最新的Go版本1.20在Go1.19发布六个月后发布。它的大部分更改都在工具链、运行时和库的实现中。一如既往,该版本保持了Go1的兼容性承诺。我们期望几乎所...

iOS 10平台SpriteKit新特性之Tile Maps(上)

简介苹果公司在WWDC2016大会上向人们展示了一大批新的好东西。其中之一就是SpriteKitTileEditor。这款工具易于上手,而且看起来速度特别快。在本教程中,你将了解关于TileE...

程序员简历例句—范例Java、Python、C++模板

个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...

Telerik UI for iOS Q3 2015正式发布

近日,TelerikUIforiOS正式发布了Q32015。新版本新增对XCode7、Swift2.0和iOS9的支持,同时还新增了对数轴、不连续的日期时间轴等;改进TKDataPoin...

ios使用ijkplayer+nginx进行视频直播

上两节,我们讲到使用nginx和ngixn的rtmp模块搭建直播的服务器,接着我们讲解了在Android使用ijkplayer来作为我们的视频直播播放器,整个过程中,需要注意的就是ijlplayer编...

IOS技术分享|iOS快速生成开发文档(一)

前言对于开发人员而言,文档的作用不言而喻。文档不仅可以提高软件开发效率,还能便于以后的软件开发、使用和维护。本文主要讲述Objective-C快速生成开发文档工具appledoc。简介apple...

macOS下配置VS Code C++开发环境

本文介绍在苹果macOS操作系统下,配置VisualStudioCode的C/C++开发环境的过程,本环境使用Clang/LLVM编译器和调试器。一、前置条件本文默认前置条件是,您的开发设备已...