Spring缓存注解@Cacheable @CacheEvit @CachePut使用介绍
作者:一灰灰 发布时间:2022-06-05 01:47:14
目录
I. 项目环境
1. 项目依赖
II. 缓存注解介绍
1. @Cacheable
2. @CachePut
3. @CacheEvict
4. @Caching
5. 异常时,缓存会怎样?
6. 测试用例
7. 小结
III. 不能错过的源码和相关知识点
0. 项目
Spring在3.1版本,就提供了一条基于注解的缓存策略,实际使用起来还是很丝滑的,本文将针对几个常用的注解进行简单的介绍说明,有需要的小伙伴可以尝试一下
本文主要知识点:
@Cacheable: 缓存存在,则使用缓存;不存在,则执行方法,并将结果塞入缓存
@CacheEvit: 失效缓存
@CachePut: 更新缓存
I. 项目环境
1. 项目依赖
本项目借助SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA + redis5.0进行开发
开一个web服务用于测试
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
</dependencies>
全程使用默认配置,redis本机,端口6379,无密码
II. 缓存注解介绍
1. @Cacheable
这个注解用于修饰方法or类,当我们访问它修饰的方法时,优先从缓存中获取,若缓存中存在,则直接获取缓存的值;缓存不存在时,执行方法,并将结果写入缓存
这个注解,有两个比较核心的设置
/**
* 与 cacheNames 效果等价
*/
@AliasFor("cacheNames")
String[] value() default {};
@AliasFor("value")
String[] cacheNames() default {};
/**
* 缓存key
*/
String key() default "";
cacheNames可以理解为缓存key的前缀,可以为组件缓存的key变量;当key不设置时,使用方法参数来初始化,注意key为SpEL表达式,因此如果要写字符串时,用单引号括起来
一个简单的使用姿势
/**
* 首先从缓存中查,查到之后,直接返回缓存数据;否则执行方法,并将结果缓存
* <p>
* redisKey: cacheNames + key 组合而成 --> 支持SpEL
* redisValue: 返回结果
*
* @param name
* @return
*/
@Cacheable(cacheNames = "say", key = "'p_'+ #name")
public String sayHello(String name) {
return "hello+" + name + "-->" + UUID.randomUUID().toString();
}
如我们传参为 yihuihui, 那么缓存key为 say::p_yihuihui
除了上面三个配置值之外,查看@Cacheable注解源码的童鞋可以看到还有condition设置,这个表示当它设置的条件达成时,才写入缓存
/**
* 满足condition条件的才写入缓存
*
* @param age
* @return
*/
@Cacheable(cacheNames = "condition", key = "#age", condition = "#age % 2 == 0")
public String setByCondition(int age) {
return "condition:" + age + "-->" + UUID.randomUUID().toString();
}
上面这个case中,age为偶数的时候,才走缓存;否则不写缓存
接下来是unless参数,从名字上可以看出它表示不满足条件时才写入缓存
/**
* unless, 不满足条件才写入缓存
*
* @param age
* @return
*/
@Cacheable(cacheNames = "unless", key = "#age", unless = "#age % 2 == 0")
public String setUnless(int age) {
return "unless:" + age + "-->" + UUID.randomUUID().toString();
}
2. @CachePut
不管缓存有没有,都将方法的返回结果写入缓存;适用于缓存更新
/**
* 不管缓存有没有,都写入缓存
*
* @param age
* @return
*/
@CachePut(cacheNames = "t4", key = "#age")
public String cachePut(int age) {
return "t4:" + age + "-->" + UUID.randomUUID().toString();
}
3. @CacheEvict
这个就是我们理解的删除缓存
/**
* 失效缓存
*
* @param name
* @return
*/
@CacheEvict(cacheNames = "say", key = "'p_'+ #name")
public String evict(String name) {
return "evict+" + name + "-->" + UUID.randomUUID().toString();
}
4. @Caching
在实际的工作中,经常会遇到一个数据变动,更新多个缓存的场景,对于这个场景,可以通过@Caching来实现
/**
* caching实现组合,添加缓存,并失效其他的缓存
*
* @param age
* @return
*/
@Caching(cacheable = @Cacheable(cacheNames = "caching", key = "#age"), evict = @CacheEvict(cacheNames = "t4", key = "#age"))
public String caching(int age) {
return "caching: " + age + "-->" + UUID.randomUUID().toString();
}
上面这个就是组合操作
从 caching::age缓存取数据,不存在时执行方法并写入缓存;
失效缓存 t4::age
5. 异常时,缓存会怎样?
上面的几个case,都是正常的场景,当方法抛出异常时,这个缓存表现会怎样?
/**
* 用于测试异常时,是否会写入缓存
*
* @param age
* @return
*/
@Cacheable(cacheNames = "exception", key = "#age")
@Cacheable(cacheNames = "say", key = "'p_yihuihui'")
public int exception(int age) {
return 10 / age;
}
根据实测结果,当age==0时,上面两个缓存都不会成功
6. 测试用例
接下来验证下缓存注解与上面描述的是否一致
@RestController
public class IndexRest {
@Autowired
private BasicDemo helloService;
@GetMapping(path = {"", "/"})
public String hello(String name) {
return helloService.sayHello(name);
}
}
上面这个主要是验证@Cacheable注解,若缓存不命中,每次返回的结果应该都不一样,然而实际访问时,会发现返回的都是相同的
curl http://localhost:8080/?name=yihuihui
失效缓存
@GetMapping(path = "evict")
public String evict(String name) {
return helloService.evict(String.valueOf(name));
}
失效缓存,需要和上面的case配合起来使用
curl http://localhost:8080/evict?name=yihuihui
curl http://localhost:8080/?name=yihuihui
剩下其他的相关测试类就比较好理解了,一并贴出对应的代码
@GetMapping(path = "condition")
public String t1(int age) {
return helloService.setByCondition(age);
}
@GetMapping(path = "unless")
public String t2(int age) {
return helloService.setUnless(age);
}
@GetMapping(path = "exception")
public String exception(int age) {
try {
return String.valueOf(helloService.exception(age));
} catch (Exception e) {
return e.getMessage();
}
}
@GetMapping(path = "cachePut")
public String cachePut(int age) {
return helloService.cachePut(age);
}
7. 小结
最后管理小结一下Spring提供的几个缓存注解
@Cacheable: 缓存存在,则从缓存取;否则执行方法,并将返回结果写入缓存
@CacheEvit: 失效缓存
@CachePut: 更新缓存
@Caching: 都注解组合
上面虽说可以满足常见的缓存使用场景,但是有一个非常重要的点没有说明,缓存失效时间应该怎么设置???
如何给每个缓存设置不同的缓存失效时间,咱么下篇博文见,我是一灰灰,欢迎关注长草的公众号一灰灰blog
III. 不能错过的源码和相关知识点
0. 项目
工程:https://github.com/liuyueyi/spring-boot-demo
源码:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/125-cache-ano
来源:https://juejin.cn/post/6980288897154547720
![](https://www.aspxhome.com/images/zang.png)
![](https://www.aspxhome.com/images/jiucuo.png)
猜你喜欢
- 定义枚举类型时本质上就是在定义一个类,只不过很多细节由编译器帮您补齐了,所以某些程度上,enum关键字的 作用就像是class或interf
- 0x00:前言参考之前的《MyBatis 中 SqlMapConfig 配置文件详解》记了一下 MyBatis 中的核心配置文件各个标签的作
- 在 Flutter 中使用图片是最基础能力之一。作为春节开工后的第一篇文章,17 做了精心准备,满满的都是干货!本文介绍如何在 Flutte
- 场景需要判断,首字母是否是英文字母。有人说,那还不简单么,StringUtils.isAlpha() 就可以搞定。 笔者也是这么想的,结果却
- 一、采用Object.toString()toString方法是java.lang.Object对象的一个public方法。在java中任何
- 一、定时任务的使用场景和常见的定时任务某个时间定时处理某个任务、发邮件、短信、消息提醒、订单通知、统计报表等定时任务划分单机定时任务:单机的
- 今天在做项目学习的过程中,需要用到文件上传,启动项目正常,访问上传post请求突然报出了一个异常,特此记录一下。1、控制台异常org.spr
- 配置绑定所谓配置绑定”就是把配置文件中的值与 JavaBean 中对应的属性进行绑定。通常,我们会把一些配置信息(例如,
- 一、Spring Boot的特点首先我们要知道 Spring Boot 在底层已经为我们添加好了很多依赖。比如我们常用的Tomcat,Spr
- 1. Ajax 概述Ajax 的英文全称是 ”Asynchronous JavaScript and XML&l
- 本文主要给大家介绍java的InputStream 流的使用。(1)FileInputstream: 子类,读取数据的通道使用步骤:1.获取
- 本文主要介绍Java Date 日期类型,以及Calendar的怎么获取时间,然后写成时间工具类里面有下面这些方法:- 时间转字符串(有默认
- 导读Lombok:可以让你的POJO代码特别简洁,不止简单在BO/VO/DTO/DO等大量使用,还有设计模式,对象对比等MybatisPlu
- 前言在阅读本文之前, 希望你可以思考一下下面几个问题, 带着问题去阅读文章会获得更好的效果。发送消息的时候, 当Broker挂掉了,消息体还
- 部分同学在使用 idea 时可能会遇到输入 sout 无法出现自动补全 System.out.println();的情况,其实 idea 默
- 图片的复制无非有两种方法,一种是图片直接上传到服务器,另外一种转换成二进制流的base64码目前限chrome浏览器使用首先以um-edit
- 什么是Dozer?Dozer是一种Java Bean到Java Bean的映射器,递归地将数据从一个对象复制到另一个对象,它是一个强大的,通
- yml文件参数的读取附上一个较为常见的application.yml文件示例server: port: 9999 u
- 对于服务器端开发人员而言,调用第三方接口获取数据,将其“代理”转化并返给客户端几乎是家常便
- 谷歌官方推出了一种侧滑菜单的实现方式(抽屉效果),即 DrawerLayout,这个类是在Support Library里的,需要加上and