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,缓存;

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

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

相关文章

蓝桥杯真题:四平方和

import java.io.*;/*先找后两个数for(int i 0; 2 * i * i < n;i)for(int j i; i * i j * j < n;j ) 再找前两个数 for(int i 0;4 * i * i < n; i )for(int j i;2 * (j * j i * i) < n;j )//这样就可以让后两个数尽量大,前两个数尽量小 这样就可以确定后…

HTML5+CSS3+Vue小实例:浪漫的心形文字动画特效

实例:浪漫的心形文字动画特效 技术栈:HTML+CSS+Vue 效果: 源码: 【HTML】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport" conte…

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

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-A 目录 2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-A 需要环境或者解析可以私信 &#xff08;二&#xff09;A 模块基础设施设置/安全加固&#xff08;200 分&…

行业分析:2023年藜麦市场竞争格局及发展现状分析

藜麦是藜科藜属植物。穗部可呈红、紫、黄&#xff0c;植株形状类似灰灰菜&#xff0c;成熟后穗部类似高粱穗。植株大小受环境及遗传因素影响较大&#xff0c;从0.3-3米不等&#xff0c;茎部质地较硬&#xff0c;可分枝可不分。单叶互生&#xff0c;叶片呈鸭掌状&#xff0c;叶缘…

Pytorch CIFAR10图像分类 Swin Transformer篇

Pytorch CIFAR10图像分类 Swin Transformer篇 文章目录 Pytorch CIFAR10图像分类 Swin Transformer篇4. 定义网络&#xff08;Swin Transformer&#xff09;Swin Transformer整体架构Patch MergingW-MSASW-MSARelative position biasSwin Transformer 网络结构Patch EmbeddingP…

数据结构学习笔记——二叉树的遍历和链式存储代码实现二叉树

目录 一、二叉树的遍历&#xff08;一&#xff09;二叉树的先序遍历&#xff08;DLR&#xff09;&#xff08;二&#xff09;二叉树的中序遍历&#xff08;LDR&#xff09;&#xff08;三&#xff09;二叉树的后序遍历&#xff08;LRD&#xff09;&#xff08;四&#xff09;先…

如何使用群晖管家结合内网穿透实现公网远程访问本地黑群晖

白嫖怪狂喜&#xff01;黑群晖也能使用群晖管家啦&#xff01; 文章目录 白嫖怪狂喜&#xff01;黑群晖也能使用群晖管家啦&#xff01;1.使用环境要求&#xff1a;2.下载安装群晖管家app3.随机地址登陆群晖管家app4.固定地址登陆群晖管家app 自己组装nas的白嫖怪们虽然也可以通…

微信小程序引入node_modules依赖

微信小程序不支持直接读取node_modules 首先在目录文件夹下cmd输入npm init命令 D:\小程序\project\calendar\calendar_1>npm init This utility will walk you through creating a package.json file. It only covers the most common items, and tries to guess sensible…

linux resin的安装

1. 下载安装包 1.1 下载地址&#xff1a;https://caucho.com/products/resin/download 这里我下载的是普通版本的resin&#xff0c;没有选resin pro 版本。 科普一下&#xff0c;从性能上来说 resin和resin pro 版本的性能没区别。 resin pro 版本的 和resin 普通版本的文件是…

无惧泄密:揭秘上海迅软DSE防拷贝大杀器!

对于企事业单位而言&#xff0c;文档的安全保护不仅要从源头上进行&#xff0c;杜绝文档在使用、传播过程中产生的泄密风险&#xff0c;同时也要对文档内容本身进行保护。为防止有心人通过拷贝、截屏、拍照等方式盗窃走重要文档内容信息的情况&#xff0c;天锐绿盾文件防泄密软…

HTTP 缓存机制

一、强制缓存 只要浏览器判断缓存没有过期&#xff0c;则直接使用浏览器的本地缓存而无需再请求服务器。 强制缓存是利用下面这两个 HTTP 响应头部&#xff08;Response Header&#xff09;字段实现的&#xff0c;它们都用来表示资源在客户端缓存的有效期&#xff1a; Cache…

Python基础快速过一遍

文章目录 一、变量及基本概念1、变量2、变量类型3、变量格式化输出4、type()函数5、input()函数6、类型转换函数7、注释 二、Python运算/字符1、算数运算2、比较运算3、逻辑运算4、赋值运算符5、转义字符6、成员运算符 三、判断/循环语句1、if判断语句2、while循环语句3、for循…

等保测评报价相差很大,里面有什么门道

等保测评报价的差异主要源于以下几点&#xff1a; 服务质量评估标准不同&#xff1a;不同的测评机构在测评过程中所提供的服务范围、深度、细节等方面可能存在差异&#xff0c;因此导致报价有所不同。一些机构可能提供全面且细致的测评服务&#xff0c;致力于提供高质量的等保测…

openGauss学习笔记-139 openGauss 数据库运维-例行维护-检查应用连接数

文章目录 openGauss学习笔记-139 openGauss 数据库运维-例行维护-检查应用连接数139.1 操作步骤139.2 异常处理 openGauss学习笔记-139 openGauss 数据库运维-例行维护-检查应用连接数 如果应用程序与数据库的连接数超过最大值&#xff0c;则新的连接无法建立。建议每天检查连…

一种结构新颖的双通带超导滤波器设计

闫鑫1,2&#xff0c;季来运1&#xff0c;张浩1,2&#xff0c;李颢毅1,2&#xff0c;王昭月1,2&#xff0c;曹凤莹1,2 &#xff08;1.天津海芯电子有限公司&#xff0c;天津300380&#xff1b;2.天津师范大学物理与材料科学学院&#xff0c;天津 300387.&#xff09; 摘要&…

前端dark主题的快速构建与切换

首先在全局css样式中增加一个 dark 模式即可&#xff0c;主要就是filter这个属性&#xff0c; invert(1);则表示100%完全反转样式&#xff0c;通俗点就是颠倒黑白&#xff0c;白的让它变成黑的&#xff0c;黑的让它变成白的。 css中的filter函数总结 filter:invert(1);数值范围…

Leetcode题库(数据库合集)_ 难度:简单

目录 难度&#xff1a;简单1. 组合两个表2. 第二高的薪水3. 第N高的薪水4. 分数排名5. 连续出现的数字6. 超过经理收入的员工7. 重新8. 寻找用户推荐人9. 销售员10. 排名靠前的旅行者11. 患某种疾病的患者12. 修复表中的名字13. 求关注者的数量14. 可回收且低脂的产品15. 计算特…

前后端参数传递总结

1、 页面参数 js传递参数 渲染表格 页面控制器&#xff08;前端&#xff09; 后端控制器 后端服务 实体赋值 2、跟踪情况

场景实践 | 法大大落地业财一体化,优化流程结构

2023 年&#xff0c;法大大作为中国电子签行业唯一上榜《2023胡润全球未来独角兽》企业&#xff0c;同时上榜“2022深圳市潜在科技独角兽企业榜单”。作为高速发展的高科技服务企业&#xff0c;法大大自2021年完成9亿元腾讯D轮融资后&#xff0c;建立了长期主义发展计划&#x…

计算机基础知识63

Django的条件查询&#xff1a;查询函数 exclude exclude&#xff1a;返回不满足条件的数据 res Author.objects.exclude(pk1) print(res) # <QuerySet [<Author: Author object (2)>, <Author: Author object (3)>]> order_by 1、按照 id 升序排序 res …