干翻Mybatis源码系列之第八篇:Mybatis二级缓存的创建和存储

news2024/11/15 19:39:36

给自己的每日一句

不从恶人的计谋,不站罪人的道路,不坐亵慢人的座位,惟喜爱耶和华的律法,昼夜思想,这人便为有福!他要像一棵树栽在溪水旁,按时候结果子,叶子也不枯干。凡他所做的尽都顺利

本文内容整理自《孙哥说Mybatis系列课程》,B站搜孙帅可以找到本人

前言

上次课讲完之后,所有一级缓存的地方分析到位了,但是一级缓存问题是不少的因为不能跨SqlSession共享。这个时候对于实战来讲意义不大,毕竟对于缓存来讲我们是希望跨SqlSession共享。

第一章:二级缓存前提

二级缓存默认关闭,开启二级缓存是有几个前提条件。

一:Mybatis-config.xml当中配置

此操作可有可无

     <settings>
         <setting name="cacheEnabled" value="true"/>
     </settings>

因为在核心配置文件当中配置的在Configurantion类当中

protected boolean cacheEnabled = true;

所以核心配置文件当中配置或者不配置都可以。

二:Mapper文件中引入二级缓存Cache标签

此操作必须有!

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dashu.dao.UserDAO">
//Cache标签
    <cache/>
    <select id="queryAllUsersByPage" resultType="User" useCache="true">
        select id,name from t_user
    </select>
</mapper>

三:属性

可配置可不配置

四:事务存在

此操作必须有!

没有使用缓存:
在这里插入图片描述
此图使用了缓存,并且第一次查询数据库,第二次命中缓存,缓存命中率0.5:
在这里插入图片描述

第二章:二级缓存如何在Mybatis当中实现的

一:二级缓存在Mybatis当中的整个流程

不论是怎么实现的,一定会使用Cache接口和核心实现类PrepetualCache,还有他们各种装饰器。

二级缓存Mybatis运行当中起作用的呢?或者说如何接入的呢?我们首先回顾下一级缓存,一级缓存接入是在BaseExecutor当中做的,基于PrepretualCache作为缓存对象,query方法进行数据查询,缓存有直接返回,缓存没有查询数据库放到缓存中在进行返回。

二级缓存和以及缓存的接入有这本质上区别,二级缓存使用的Excutor是CachingExecutor(Executor接口的实现类),CachingExcutor是SimpleExecutor和ReuseExecutor和BatchExecutor的装饰器。

装饰器:为目标增强核心功能,就是为了SimpleExecutor和ReuseExecutor和BatchExecutor增强他们的缓存这个核心功能的。这三个Executor是访问数据库的,CachingExcutor作为他们的装饰器就是为了增强他们的查询功能、提升查询效率。CachingExcutor在这里也是采用了一种套娃的方式:

public class CachingExecutor implements Executor {
  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
}

所以二级缓存使用的时候一定是:

CachingExecutor CachingExecutor = new CachingExecutor(SimpleExecutor);

那么这一步创建操作是在哪里做的呢?Configuration当中。Configuration这个核心类一方面封装核心配置文件和MappedStatements,他还需要创建Mybatis当中所有的核心对象:Executor和StatementHandler

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor 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) {//setting里边或者是Configuration当中的cacheEnabled = true的那个。
      //这个属性是在Configuration当中被保存并且被使用。
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

CachingExecutor是在Configuration当中的newExecutor方法里边,基于cacheEnabled判断是否被创建。

public class CachingExecutor implements Executor {
  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();
 }
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    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 {
    Cache cache = ms.getCache();
    //写没写Cache标签。
    if (cache != null) {
     //<SELET标签当中配置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) {
        //写Cache标签,但是第一次查询缓存中没有数据。
          list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          //放到二级缓存当中。
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    //Mapper文件中写没写Cache数据库
    return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

补充:
我们设计一个查询方法的时候能够涵盖所有的查询可能,返回值设计成List是最合理的。那为什么不能用Set呢?是因为List是有序的,为什么一定要体现到顺序性能?想想有没有需要排序的场景就知道了。那用treeSet行么?treeSet不也是有序的么?也是不行的,TreeSet是排序,List是有序,有序是先来后到,排序是排序规则。

在这里插入图片描述

二:级缓存对于增删改的操作

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

设计到对于数据的增删改之后,会首先清除二级缓存,避免脏数据的产生。

第三章:二级缓存的创建

我们的缓存数据是放到了Cache接口对应的实现类当中,那么这这些接口的实现类的实例是在什么时候创建的?以及怎么创建的呢?这是我们接下来要解决的问题。

我们可以搜索一个源码:useNewCache方法,这个方法是在:MapperBuilderAssistant这个类当中。MapperBuilderAssistant这个类是MappedStateMent的创建助手。那么这个方法都会被谁来调用呢?快捷键ctrl+alt+h查看方法的被调用路径。

在这里插入图片描述
这两个方法都会调用useNewCache方法,那么有什么区别呢?XMLMapperBuilder对应的XML文件这种形式,MapperAnnotaionBuilder对应的注解的这中形式。

知识补充:
Mybatis当中映射是有两种方式的。一种是基于注解,一种是基于xml文件。

一:什么时候创建?

  private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      //当解析到cache标签之后就会帮我们创建Cache
      cacheElement(context.evalNode("cache"));
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

在这里插入图片描述

当代码解析到Mapper.xml文件中的Cache标签之后就会创建Cache对象。调用的方法就是useNewCache

二:如何实现呢?

  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    Cache cache = new CacheBuilder(currentNamespace)
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        .build();
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

上述代码中是一个典型的构建者设计模式,构建者设计模式有两个约定,1:一定是什么xxxAdapter,2:最终进行操作一定是他的build方法。构建者设计模式最终的目的就是为了创建一个对象。这就很类似我们的工厂。

Mybatis在创建我们的二级缓存的时候,在useNewCache构建者设计模式,使用构建者和工厂创建对象有什么区别呢?

工厂一般是Factory.xxx(),构建者设计模式一般是new xxxBuilder().build();
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build();这里应用的也是构建者设计模式。

构建者设计模式强调的是零件组装成一个整体对象。接下来我们去研究一下build方法。

  public Cache build() {
    //设置默认Cache.
    setDefaultImplementations();
    //解决自定义Cache
    Cache cache = newBaseCacheInstance(implementation, id);
    setCacheProperties(cache);
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      for (Class<? extends Cache> decorator : decorators) {
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      //<cache eviction="FIFO" blocking="" readOnly="" size="" flushInterval=""/>
      //有了这些配置属性,这个方法当中才会加上对应的装饰器。
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

setDefaultImplementations方法揭晓

  private Class<? extends Cache> implementation;
  private final List<Class<? extends Cache>> decorators;
 
  private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }

这个方法还是很简单的,设置了默认的实现,如果我们不做任何配置的话,二级缓存使用的是PerpetualCache,装饰器只有一层,只用的是LruCache

Cache cache = newBaseCacheInstance(implementation, id);方法解决自定义Cache

        <cache type="org.mybatis.caches.ehcache.EhcacheCache">
            <property name="" value=""/>
            <property name="" value=""/>
            <property name="" value=""/>
            <property name="" value=""/>
            <property name="" value=""/>
        </cache>
  private Cache newBaseCacheInstance(Class<? extends Cache> cacheClass, String id) {
    Constructor<? extends Cache> cacheConstructor = getBaseCacheConstructor(cacheClass);
    try {
      return cacheConstructor.newInstance(id);
    } catch (Exception e) {
      throw new CacheException("Could not instantiate cache implementation (" + cacheClass + "). Cause: " + e, e);
    }
  }

本质上就是基于反射创建了一个对象

在这里插入图片描述
在这里插入图片描述

第四章:二级缓存的存储

接下来我们要研究明白的问题是创建好的Cache对象是要放到哪个位置呢?
答案是显而易见的存放在了MappedStatement当中。通过MappedStatement.getCache()方法来获取缓存Cache对象。

CacheExcutor二级缓存操作时从MappedStatement获取Cache对象,并获取缓存数据。

问题:
Mybatis当中存在一级缓存也存在二级缓存,那么Mybatis进行查询的时候是先查一级缓存还是二级缓存呢?

Configuration创建Excutor的时候,走newExcutor方法。返回给我们的Executor方法是CachingExecutor,源码如下:

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor 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) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

cacheEnabled默认开着呢,返回的是CachingExecutor,然后里边返回的是SimpleExecutor做他的娃。我们又知道SimpleExecutor extends BaseExecutor,SimpleExecutor部分内容是交给BaseExecutor完成的,

BaseExecutor

tmc.get是二级缓存,local是一级缓存。所以是先走二级,在走一级缓存

debug证明我们分析的是正确的。

去看CachingExecutor当中的query方法即可。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/605572.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++之---树/数据结构

一、树 什么是树&#xff1f; 1.1 树&#xff08;Tree&#xff09;是n&#xff08;n>0&#xff09;个结点的有限集。n0时称为空树。在任意一棵非空树中&#xff1a; &#xff08;1&#xff09; 有且仅有一个特定的称为根&#xff08;Root&#xff09;的结点&#xff1b; &am…

CodeForces..最新行动.[中等].[遍历].[判断]

题目描述&#xff1a; 题目解读&#xff1a; "最近操作"字段会显示最近操作的n个文件。 最初有编号文件1&#xff0c;2&#xff0c;... n在"最近操作"字段&#xff0c;还有其他无限多个文件不在。 当某个文件pi发生操作时&#xff1a; 如果它位于“最近…

小红书账号矩阵优化软件

小红书账号矩阵优化软件 大家有关注过品牌在⼩红书上的打法有哪些吗&#xff1f; #品牌营销#小红书运营#爆文拆解#品牌投放#爆品打造 我们如果确定了我们要去做小红书&#xff0c;那我到底该怎么去做&#xff1f;现在小红书对我们目前这些品牌来说&#xff0c;你们是作为把它…

Allegro16.6详细教程(二)

R.3-D Viewer 3-D Viewer,可以直接在allegro中看到board file的3-D顯示效果。3-D Viewer對於PCB Editor Products,只有環境變數中的OpenGL顯示功能開啟後才有效,而對於APD/SiP是無效的。 2.3-D viewer是在一個獨立的視窗中打開的。3-D environment環境支援多種顯示內容的過…

Spring Cloud Alibaba - Nacos源码分析(二)

目录 一、Nacos服务端服务注册 1、服务端调用接口 2、服务注册 instanceServiceV2.registerInstance EphemeralClientOperationServiceImpl.registerInstance ServiceManager clientManager Client实例AbstractClient ClientOperationEvent.ClientRegisterServiceEven…

2023《中国好声音》全国巡演Channel[V]歌手大赛广州赛区半决赛圆满举行!

2023年5月27-28日&#xff0c;由腾扬广告、Channel[V]、盛娱星汇联合主办的2023《中国好声音》全国巡演Channel[V]歌手大赛广州赛区半决赛在广州番禺天河城正式打响&#xff0c;自广州赛区赛事启动以来&#xff0c;汇集了近五千名音乐人参与其中&#xff0c;历经2个多月、超40场…

【数据库复习】第七章 数据库设计

数据库设计的过程(六个阶段) ⒈需求分析阶段 准确了解与分析用户需求&#xff08;包括数据与处理&#xff09; 最困难、最耗费时间的一步 ⒉概念结构设计阶段 整个数据库设计的关键 通过对用户需求进行综合、归纳与抽象&#xff0c;形成一个独立于具体DBMS的概念模型 ⒊…

基于微信小程序蛋糕店商城管理系统的设计与实现

1&#xff1a;后端采用技术 SpringBoot 、Mybatis、Mybatis-plus、Redis、阿里云短信息服务、Hutool 邮箱服务、WebSocket通讯服务、OSS对象存储服务、支付宝沙箱服务&#xff0c;接口简单限流、简单定时任务。。。。。。 2&#xff1a;前端采用技术 Vue2、Vue2-uploader组件、…

[图表]pyecharts模块-日历图

[图表]pyecharts模块-日历图 先来看代码&#xff1a; import random import datetimeimport pyecharts.options as opts from pyecharts.charts import Calendarbegin datetime.date(2017, 1, 1) end datetime.date(2017, 12, 31) data [[str(begin datetime.timedelta(d…

Leetcode 110-平衡二叉树

1. 递归法求解 递归三部曲&#xff1a; 确定递归函数的参数及其返回值确定终止条件确定单层递归逻辑 深度&#xff1a;从上往下 高度&#xff1a;从下往上 1.1 根据深度求解 构建求二叉树节点深度的函数&#xff08;后序遍历&#xff09;递归求该树是否是平衡二叉树&#…

国产化麒麟linux系统开发编译常见问题汇总

团队自研股票软件关注威信龚总号&#xff1a;QStockView&#xff0c;下载 1 问题处理 1.1 Unknown module in QT:QJsonDocument 缺少QJsonDocument 解决方法&#xff1a; Pro文件中加上 QTcore; 播放器库问题 1.2 代码中汉字乱码需要设置文件编码格式 原因分析&…

2023-06-03:redis中pipeline有什么好处,为什么要用 pipeline?

2023-06-03&#xff1a;redis中pipeline有什么好处&#xff0c;为什么要用 pipeline&#xff1f; 答案2023-06-03&#xff1a; Redis客户端执行一条命令通常包括以下四个阶段&#xff1a; 1.发送命令&#xff1a;客户端将要执行的命令发送到Redis服务器。 2.命令排队&#…

内网安全:Cobalt Strike 工具 渗透多层内网主机.(正向 || 反向)

内网安全&#xff1a;Cobalt Strike 工具 渗透多层内网主机. Cobalt Strike 是一款以 metasploit 为基础的 GUI 的框架式渗透工具&#xff0c;又被业界人称为 CS。拥有多种协议主机上线方式&#xff0c;集成了端口转发&#xff0c;服务扫描&#xff0c;自动化溢出&#xff0c;…

Docker容器化Java程序

&#x1f497;wei_shuo的个人主页 &#x1f4ab;wei_shuo的学习社区 &#x1f310;Hello World &#xff01; Docker容器化Java程序 Docker&#xff1a;用于创建和管理容器的开源平台 Java运行环境&#xff1a;Java是一个跨平台的编程语言&#xff0c;因此在CentOS系统中需要安…

一个帮助写autoprefixer配置的网站

前端需要用到postcss的工具&#xff0c;用到一个插件叫autoprefixer&#xff0c;这个插件能够给css属性加上前缀&#xff0c;进行一些兼容的工作。 如何安装之类的问题在csdn上搜一下都能找到&#xff08;注意&#xff0c;vite是包含postcss的&#xff0c;不用在项目中安装pos…

[图表]pyecharts模块-柱状图2

[图表]pyecharts模块-柱状图2 先来看代码&#xff1a; from pyecharts import options as opts from pyecharts.charts import Bar from pyecharts.faker import Fakerx Faker.dogs Faker.animal xlen len(x) y [] for idx, item in enumerate(x):if idx < xlen / 2:y…

Visual Studio Code里如何运行html (Windows 10 和 Mac OS)

在Web 开发时&#xff0c;作为Web 开发基本都是从编写 HTML 网页开始的。这篇文章讲的是如何起步配置开发环境来运行 HTML 代码。 在Windows和Mac 的 VS Code中都可以运行 HTML。 打开VS Code&#xff0c;在VS Code中安装&#xff0c;Code Runner&#xff0c; 如下所示 2、这…

【群智能算法改进】一种改进的算术优化算法 改进算术优化算法 改进AOA[1]【Matlab代码#37】

文章目录 【获取资源请见文章第5节&#xff1a;资源获取】1. 基础算术优化算法2. 改进算术优化算法2.1 随机概率因子2.2 强制切换机制 3. 部分代码展示4. 仿真结果展示5. 资源获取 【获取资源请见文章第5节&#xff1a;资源获取】 1. 基础算术优化算法 算术优化算法是一类基于…

mount -l | grep bpf

BPF & Cillum mount -l | grep bpfBPF&#xff08;Berkeley Packet Filter&#xff09;文件系统netfilter和tcprofiling和tracingHTTP、gRPC和Kafka等协议VXLAN组网模式BGP&#xff08;Border Gateway Protocol&#xff09; mount -l | grep bpf 这是一个通过运行mount -l…

Linux 实操篇-Linux 磁盘分区、挂载

Linux 实操篇-Linux 磁盘分区、挂载 Linux 分区 原理介绍 Linux 来说无论有几个分区&#xff0c;分给哪一目录使用&#xff0c;它归根结底就只有一个根目录&#xff0c;一个独立且唯一的文件结构, Linux 中每个分区都是用来组成整个文件系统的一部分。Linux 采用了一种叫“载…