spring cache 学习 —— @Cacheable 使用详解

1. 功能说明

  @Cacheable 注解在方法上,表示该方法的返回结果是可以缓存的。也就是说,该方法的返回结果会放在缓存中,以便于以后使用相同的参数调用该方法时,会返回缓存中的值,而不会实际执行该方法。

  注意,这里强调了一点:参数相同。这一点应该是很容易理解的,因为缓存不关心方法的执行逻辑,它能确定的是:对于同一个方法,如果参数相同,那么返回结果也是相同的。但是如果参数不同,缓存只能假设结果是不同的,所以对于同一个方法,你的程序运行过程中,使用了多少种参数组合调用过该方法,理论上就会生成多少个缓存的 key(当然,这些组合的参数指的是与生成 key 相关的)。下面来了解一下 @Cacheable 的一些参数:

2. cacheNames & value

  @Cacheable 提供两个参数来指定缓存名:value、cacheNames,二者选其一即可。这是 @Cacheable 最简单的用法示例:

@Override
@Cacheable("menu")
public Menu findById(String id) {
    Menu menu = this.getById(id);
    if (menu != null){
        System.out.println("menu.name = " + menu.getName());
    }
    return menu;
}

  在这个例子中,findById 方法与一个名为 menu 的缓存关联起来了。调用该方法时,会检查 menu 缓存,如果缓存中有结果,就不会去执行方法了。

3. 关联多个缓存名

  其实,按照官方文档,@Cacheable 支持同一个方法关联多个缓存。这种情况下,当执行方法之前,这些关联的每一个缓存都会被检查,而且只要至少其中一个缓存命中了,那么这个缓存中的值就会被返回。示例:

@Override
    @Cacheable({"menu", "menuById"})
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

---------
@GetMapping("/findById/{id}")
public Menu findById(@PathVariable("id")String id){
    Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a");
    Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75");
    return menu0;
}

  为了直观起见,直接将 id 参数写到代码里。现在,我们来测试一下,看一下结果:

    

4. key & keyGenerator

  一个缓存名对应一个被注解的方法,但是一个方法可能传入不同的参数,那么结果也就会不同,这应该如何区分呢?这就需要用到 key 。在 spring 中,key 的生成有两种方式:显式指定和使用 keyGenerator 自动生成。

  4.1. KeyGenerator 自动生成

    当我们在声明 @Cacheable 时不指定 key 参数,则该缓存名下的所有 key 会使用 KeyGenerator 根据参数 自动生成。spring 有一个默认的 SimpleKeyGenerator ,在 spring boot 自动化配置中,这个会被默认注入。生成规则如下:

      a. 如果该缓存方法没有参数,返回 SimpleKey.EMPTY ;

      b. 如果该缓存方法有一个参数,返回该参数的实例 ;

      c. 如果该缓存方法有多个参数,返回一个包含所有参数的 SimpleKey ;

    默认的 key 生成器要求参数具有有效的 hashCode() 和 equals() 方法实现。另外,keyGenerator 也支持自定义, 并通过 keyGenerator 来指定。关于 KeyGenerator 这里不做详细介绍,有兴趣的话可以去看看源码,其实就是使用 hashCode 进行加乘运算。跟 String 和 ArrayList 的 hash 计算类似。

  4.2. 显式指定 key

    相较于使用 KeyGenerator 生成,spring 官方更推荐显式指定 key 的方式,即指定 @Cacheable 的 key 参数。

    即便是显式指定,但是 key 的值还是需要根据参数的不同来生成,那么如何实现动态拼接呢?SpEL(Spring Expression Language,Spring 表达式语言) 能做到这一点。下面是一些使用 SpEL 生成 key 的例子。

@Override
    @Cacheable(value = {"menuById"}, key = "#id")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

    @Override
    @Cacheable(value = {"menuById"}, key = "'id-' + #menu.id")
    public Menu findById(Menu menu) {
        return menu;
    }

    @Override
    @Cacheable(value = {"menuById"}, key = "'hash' + #menu.hashCode()")
    public Menu findByHash(Menu menu) {
        return menu;
    }

    结果:

      

    显示指定的好处在于,直观明了,看到代码就能想象生成的 key 是什么样。而且 SpEL 也很强大。关于 SpEL 的详细用法,这里不详述,可以参考官方文档:

      Redirecting...

  注意:官方说 key 和 keyGenerator 参数是互斥的,同时指定两个会导致异常

5. cacheManager & cacheResolver

  CacheManager,缓存管理器是用来管理(检索)一类缓存的。通常来讲,缓存管理器是与缓存组件类型相关联的。我们知道,spring 缓存抽象的目的是为使用不同缓存组件类型提供统一的访问接口,以向开发者屏蔽各种缓存组件的差异性。那么  CacheManager 就是承担了这种屏蔽的功能。spring 为其支持的每一种缓存的组件类型提供了一个默认的 manager,如:RedisCacheManager 管理 redis 相关的缓存的检索、EhCacheManager 管理 ehCache 相关的缓等。

  CacheResolver,缓存解析器是用来管理缓存管理器的,CacheResolver 保持一个 cacheManager 的引用,并通过它来检索缓存。CacheResolver 与 CacheManager 的关系有点类似于 KeyGenerator 跟 key。spring 默认提供了一个 SimpleCacheResolver,开发者可以自定义并通过 @Bean 来注入自定义的解析器,以实现更灵活的检索。

  大多数情况下,我们的系统只会配置一种缓存,所以我们并不需要显式指定 cacheManager 或者 cacheResolver。但是 spring 允许我们的系统同时配置多种缓存组件,这种情况下,我们需要指定。指定的方式是使用 @Cacheable 的 cacheManager 或者 cacheResolver 参数。

  注意:按照官方文档,cacheManager 和 cacheResolver 是互斥参数,同时指定两个可能会导致异常。

6. sync

  是否同步,true/false。在一个多线程的环境中,某些操作可能被相同的参数并发地调用,这样同一个 value 值可能被多次计算(或多次访问 db),这样就达不到缓存的目的。针对这些可能高并发的操作,我们可以使用 sync 参数来告诉底层的缓存提供者将缓存的入口锁住,这样就只能有一个线程计算操作的结果值,而其它线程需要等待,这样就避免了 n-1 次数据库访问。

  sync = true 可以有效的避免缓存击穿的问题。

7. condition

  调用前判断,缓存的条件。有时候,我们可能并不想对一个方法的所有调用情况进行缓存,我们可能想要根据调用方法时候的某些参数值,来确定是否需要将结果进行缓存或者从缓存中取结果。比如当我根据年龄查询用户的时候,我只想要缓存年龄大于 35 的查询结果。那么 condition 能实现这种效果。

  condition 接收一个结果为 true 或 false 的表达式,表达式同样支持 SpEL 。如果表达式结果为 true,则调用方法时会执行正常的缓存逻辑(查缓存-有就返回-没有就执行方法-方法返回不空就添加缓存);否则,调用方法时就好像该方法没有声明缓存一样(即无论传入了什么参数或者缓存中有些什么值,都会执行方法,并且结果不放入缓存)。下面举个例子:

  我们看一下数据库,以这两条数据为例:

    

  我们首先定义一个带条件的缓存方法:

@Override
    @Cacheable(value = {"menuById"}, key = "#id", condition = "#conditionValue > 1")
    public Menu findById(String id, Integer conditionValue) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

  然后分两种情况调用(为了直观可见,直接将 id 写在代码中):

@GetMapping("/findById/{id}")
    public Menu findById(@PathVariable("id")String id){
        Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a", 0);
        Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75", 2);
        return menu0;
    }

  然后我们请求一下,看看缓存中的结果和控制台打印:

    

   

  可以看到,两次请求都执行方法(因为原来缓存中都没有数据),但是只有“微服务测试2”缓存了。这说明,只有满足 condition 条件的调用,结果才会被缓存。接下来我们再请求一遍,看下结果和打印:

    

 

  可以看到,“微服务测试2”由于已经有了缓存,所以没有再执行方法体。而“微服务测试0”又一次执行了。

8. unless

  执行后判断,不缓存的条件。unless 接收一个结果为 true 或 false 的表达式,表达式支持 SpEL。当结果为 true 时,不缓存。举个例子:

  我们先清除 redis 中的数据。然后看看 mysql 中的数据: 

  然后编写一个缓存方法(在该方法中,result代表方法的返回值。关于):

@Override
    @Cacheable(value = {"menuById"}, key = "#id", unless = "#result.type == 'folder'")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

  然后调用该方法:

@GetMapping("/findById/{id}")
    public Menu findById(@PathVariable("id")String id){
        Menu menu0 = menuService.findById("fe278df654adf23cf6687f64d1549c0a");
        Menu menu2 = menuService.findById("fb6106721f289ebf0969565fa8361c75");
        return menu0;
    }

  看看缓存结果和打印:

    

    

  可以看到,两次都执行了方法体(其实,unless 条件就是在方法执行完毕后调用,所以它不会影响方法的执行),但是结果只有 menu.type = 'page' 的缓存了,说明 unless 参数生效了。

9. condition VS unless ?

  既然 condition 和 unless 都能决定是否进行缓存,那么同时指定这两个参数并且结果相冲突的时候,会怎么样呢?我们来试一试。

  首先清除 redis 数据,然后在缓存方法上加上 condition="true",如:

@Override
    @Cacheable(value = {"menuById"}, key = "#id", condition = "true", unless = "#result.type == 'folder'")
    public Menu findById(String id) {
        Menu menu = this.getById(id);
        if (menu != null){
            System.out.println("menu.name = " + menu.getName());
        }
        return menu;
    }

  其它代码不变,我们来看一下缓存结果和打印:

    

  

   可以看到,虽然两次调用都执行了,但是,type='folder' 的还是被排除了。说明这种情况下,unless 比 condition 优先级要高。接下来我们把 condition="false",再来试试,结果:

    

     

  可以看到,两次调用的结果都没有缓存。说明在这种情况下,condition 比 unless 的优先级高。总结起来就是:

    condition 不指定相当于 true,unless 不指定相当于 false

    当 condition = false,一定不会缓存;

    当 condition = true,且 unless = true,不缓存;

    当 condition = true,且 unless = false,缓存;

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

上门预约洗鞋店小程序

互联网洗鞋店小程序开发,结合洗鞋行业线下实际运营情况和经验,专为洗鞋人、洗鞋店打造的高效、实用、有价值的洗鞋私域流量管理软件系统。 帮助洗鞋人建立自己的私域流量,实现会员用户管理,用户与商家点对点互联互通,提…

okhttp导致的内存溢出(OOM)sun.security.ssl.SSLSocketImpl

使用分析工具:MAT(Memory Analyzer Tool)、JvisualVM占用内存:sun.security.ssl.SSLSocketImpl 一、 项目场景: 功能:一个定时任务(xxl-job)采用线程池的方式多线程请求第三方拉取数据,网络框架使用okhttp3。 问题&am…

从零开发短视频电商 在AWS上用SageMaker部署开源模型并用Java SDK调用

文章目录 1.创建AWS账户2.登录AWS3.创建域4.部署模型方式一 使用JumpStart可视化界面部署内置的模型方式二 采用python脚本部署私有模型5.调用模型AWS Java SDK调用Http调用6.监控7.自动扩缩容1.创建AWS账户 需要准备好邮箱一个,支持visa功能的信用卡一个。然后到aws上自己去…

手动部署1个Cloud Run service

什么是Cloud Run 来自chatgpt: Google Cloud Run 是一项全托管的服务器托管平台,它允许您在容器化的环境中运行无服务器应用程序。Cloud Run 提供了一种简单而灵活的方式来构建、部署和扩展应用程序,无需管理底层基础设施。 以下是 Cloud …

Linux-Linux安装JDK及配置环境 及 遇到的问题

下载linux环境对应的JDK的tar.gz包 配置JDK环境:编辑 sudo vim /etc/profile 在文件的最下方,填写 export JAVA_HOME/usr/local/src/software/jdk1.8 export CLASSPATH.:$JAVA_HOME/lib/tools.jar export PATH$JAVA_HOME/bin:$PATH 执行生效命令&…

2024年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-B卷

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-B卷 2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-B卷A 模块基础设施设置/安全加固(200 分)A-1:登录安全加固(Windows…

电商店铺搬家API接口,轻松搬运淘宝1688京东拼多多等平台全站商品丨商品详情业数据接口丨搜索商品列表数据接口

轻松搬运淘宝、1688、京东、拼多多等平台全站商品的API接口通常是由各平台提供的官方开发工具或第三方工具提供的。 对于淘宝和1688平台,可以使用淘宝开放平台(Open Platform)提供的API接口来访问和操作商品信息。淘宝开放平台提供了丰富的A…

基于通义千问和向量数据构建问答知识库

参考:Java从0到1构建基于ChatGPT向量数据库的检索增强生成模型RAG-02 - 知乎 (zhihu.com) 1、先开通 阿里云的向量检索服务 如何开通向量检索服务并创建API-KEY_向量检索服务-阿里云帮助中心 (aliyun.com) 按流程申请 最后需要申请API-KEY 安装DashVector SDK M…

Windows的常用cmd命令总结

文章目录 一.盘符切换二: cd命令(打开文件/文件夹)三:查看目录四.创建和删除文件夹五.查看本机ip地址六.清除当前屏幕七.复制文件到另一个地方八.移动文件到另一个地方九.删除文件(不能删除文件夹)十.测试网络连接十一.停止任务进程Windows快捷键总结大全…

Linux 文件管理

内容概述 1 文件系统目录结构 存放的是内存中正在运行的系统状态信息,数据不在硬盘而是在内存中 echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all1.1 文件系统的目录结构 Linux 操作系统 ext / xfs 文件系统是区分大小写,大小写敏感 Linux的文件…

RPG项目01_场景及人物动画管理器

基于“RPG项目01_UI登录”,新建一个文件夹名为Model(模型) 将资源场景拖拽至Model中 找到相应场景双击进入 红色报错部分Clear清掉即可,我们可以重做 接下来另存场景 起名为Game 点击保存 场景就保存至Scene中了 在文件夹下新创建…

SpringBoot Bean解析

Bean解析 IOC介绍 松耦合灵活性可维护 注解方式配置Bean 实现方式1: Component声明,直接类上进行添加注解, 同时保证包扫描能扫到即可实现方式2: 配置类中使用Bean Configuration public class BeanConfiguration implements SuperConfiguration{Bean("dog")Ani…

探索云原生时代:技术驱动的业务架构革新

一、引言 在数字化浪潮中,云原生技术已成为推动企业快速创新的重要动力。本文将深入探讨云原生的核心理念、技术架构以及其在实际业务环境中的应用,带领读者深入理解云原生技术的复杂性和优势。 云原生技术的定义 云原生(Cloud Native&…

HarmonyOS脚手架:UI组件之文本和图片

前言 关于HarmonyOS脚手架,本篇是系列的第二篇,主要实现UI组件文本和图片的常见效果查看,本身功能特别的简单,其目的也是很明确,方便大家根据效果查看相关代码实现,可以很方便的进行复制使用,当…

cmake和vscode 下的cmake的使用详解(二)

第四讲: GDB 调试器 前言: GDB(GNU Debugger) 是一个用来 调试 C/C 程序 的功能强大的 调试器 ,是 Linux 系统开发 C/C 最常用的调试器 程序员可以 使用 GDB 来跟踪程序中的错误 ,从而减少程序员的工作量。 Linux 开发 C/C …

CSS——基础选择器、文字控制属性

1、CSS定义 层叠样式表(Cascading Style Sheets,缩写为CSS),是一种 样式表 语言,用来描述 HTML 文档的呈现(美化内容)。 书写位置:title 标签下方添加style双标签,styl…

gitlab高级功能之CI/CD组件 - 实践(二)

上一篇主要讲解了CI/CD组件的原理,看起来稍微有一点枯燥,那么接下来给大家演示下如何使用。 案例 创建一个项目(README.md,template目录) 案例1 step1: 在template中新建yml文件,cat templates/test-st…

externalTrafficPolicy 再探

一 externalTrafficPolicy 属性再探 一次网络不通"争吵"引发的思考 K8s中的external-traffic-policy是什么? 说明: 阿里云的 EDAS 和ACK的默认行为不一样 Kubernetes(二十三)Service(二)会话保持和获取客户端的ip 访问 externalTrafficPolicy 为 L…

用于药物发现的知识图谱

我在GitHub上搜索了一下,发现有一些项目涉及到知识图谱在药物发现领域的应用。 一个项目是 **KDD2023_KaGML_DrugDiscovery_Tutorial**,它是一个关于药物发现的教程,介绍了如何使用知识增强的图机器学习(KaGML)来进行…

springboot+vue志愿者在线报名服务管理系统java毕业设计源码+数据库

vuespringboot志愿服务管理系统 本项目是springbootvueElementuimysql源码 开发工具,idea和eclipse都可以,MySQL 源码下载地址 https://download.csdn.net/download/yibo2022/88401958?spm1003.2166.3001.6637.3https://download.csdn.net/download/yibo2022/884…