【Redis】基于Spring Cache + Redis + Jackson的注解式自动缓存方案保姆式教程(2022最新)
你是否不想再重复使用 RedisTemplate 来手动地来操纵缓存?你是否苦于不知道如何配置 Redis 配置类中的序列化器和反序列化器?你是否想如何才能得到纯净可读的 JSON 格式的 Redis value ?你是否想知道 Spring Cache 和 Redis 如何搭配使用实现注解式开发缓存?你是否想知道 Spring Cache 中有哪些注解?它们分别是怎么使用的?恭喜你,找到宝藏了。
目录
前言
- 你是否不想再重复使用
RedisTemplate
来手动地来操纵缓存? - 你是否苦于不知道如何配置 Redis 配置类中的序列化器和反序列化器?
- 你是否想如何才能得到纯净可读的 JSON 格式的 Redis value ?
- 你是否想知道 Spring Cache 和 Redis 如何搭配使用实现注解式开发缓存?
- 你是否想知道 Spring Cache 中有哪些注解?它们分别是怎么使用的?
- 恭喜你,找到宝藏了。本文将一站式教你解决上述所有问题。
缓存方式选择的考量
- 目前我学习到的使用 Redis 缓存的方式有两种:
- 【原生】使用
StringRedisTemplate
操纵 Redis ,再使用 Hutool 工具包手动地 (反) 序列化。好处是不需要写RedisConfig
配置类,可以得到纯净的 JSON Value 。缺点是出现大量 Redis 的冗余代码。 - 【Spring Cache】在业务层方法上添加注解即可自动完成缓存的增删查操作。优点是简化代码,缺点是需要写复杂的配置类。
- 【原生】使用
-
对于上面两种 Redis 缓存方式,选择哪一种,根据我浅薄的经验谈谈:
-
对于业务逻辑简单,方法返回值就是要缓存的数据
value
,且方法的入参或者返回值适合作为key
的业务方法,适合使用 Spring Cache 简化缓存开发。 -
对于业务逻辑复杂、粒度更细、方法内部的某一中间值作为要缓存的
value
,那么 Spring Cache 就不太适合了。这时选择原生的StringRedisTemplate
精准操纵 Redis + Hutool 工具包手动 (反) 序列化是更明智的选择。如下代码所示:// 注入StringRedisTemplate @Autowired private StringRedisTemplate redisTemplate; // (批量)套餐启售/停售 @Override public Boolean updateStatus(Integer status, List<Long> ids) { ...省略; // 用原生方法以细粒度的方式删除该类套餐下的缓存 // 构造keys Set<String> keys = setmeals.stream().map(setmeal -> { // 获取每个套餐的类别ID Long categoryId = setmeal.getCategoryId(); // 使用Hutool工具包序列化成JSON字符串 return JSONUtil.toJsonStr(categoryId) + "-1"; }).collect(Collectors.toSet()); // 由于不能重复,因为使用Set集合收集结果 // 使用StringRedisTemplate批量删除缓存 redisTemplate.delete(keys); // 3.批量修改 return this.updateBatchById(setmeals); }
1. Spring Cache is All You Need
- 使用 Spring Cache 可以简化缓存优化的开发。
2. Spring Cache介绍
-
Spring Cache 是 Spring 提供的一整套的缓存解决方案,它不是具体的缓存实现,它只提供一整套的接口和代码规范、配置、注解等,用于整合各种缓存方案,比如 Redis、Caffeine、Guava Cache、Ehcache。使用注解方式替代原有硬编码方式缓存,语法更加简单优雅!
-
Spring Cache 是一个框架,实现了基于注解的缓存功能,只需要简单地加一个注解,就能实现缓存功能。Spring Cache 提供了一层抽象,底层可以切换不同的 cache 实现。
-
Spring Cache 是通过
CacheManager
接口来统一不同的缓存技术。CacheManager
是 Spring 提供的各种缓存技术抽象接口。针对不同的缓存技术需要实现不同的 CacheManager:CacheManager 描述 EhCacheCacheManager 使用EhCache作为缓存技术 GuavaCacheManager 使用Google的GuavaCache作为缓存技术 RedisCacheManager 使用Redis作为缓存技术
3. Spring Cache常用注解
注解 | 功能 | 添加位置 |
---|---|---|
@EnableCaching | 开启缓存注解功能 | Spring Boot启动类上 |
@Cacheable | 在方法执行前Spring先查看缓存中是否有数据。如果有数据,则直接返回缓存中的数据;若没有数据,调用方法并将方法返回值放到缓存中 | 业务层的查询方法上 |
@CachePut | 将方法的返回值放到缓存中 | 业务层的新增方法上 |
@CacheEvict | 将一条或多条数据从缓存中删除 | 业务层的修改和删除方法上 |
1)@CachePut注解的使用
@CachePut
注解中有四个入参:
入参 | 说明 |
---|---|
String value | 配置缓存【分区】,相当于缓存的标示,每个缓存【分区】下可以有多个 key |
String key | 配置缓存的【分区】下的具体表示,此处支持SpringEL 表达式 (后面会介绍),动态命名 |
String cacheManeger | String 选择配置类中的缓存配置对象 beanname,不选走默认 |
String condition | 注解生效条件, 支持 SpringEL 表达式 例如: #result != null 结果不为null,才进行缓存 |
- 为了动态地构建
key
的值,key
支持 Spring 表达式语言 SpringEL (Spring Expression Language) 。#
为 SpringEL 的开始标识,固定写法。 - Spring Cache 的使用核心在于设计如何动态地计算
key
的值,下面总结了key
的四种常用的构造方法:
key的写法 | 说明 |
---|---|
key = “#p0.id” | 把第[0]个入参的属性 id 作为key |
key = “#user.id” | 把入参 User user 的属性 id 作为key |
key = “#root.args[0].id” | 把第[0]个入参的属性 id 作为key |
key = “#result.id” | 把返回值 User user 的属性 id 作为key |
-
举例:下面的代码展示了
save()
方法,把返回值user
的 ID 作为 key 。@CachePut(value = "userCache", key = "#result.id") @PostMapping public User user(User user) { userService.save(user); return user; }
2)@CacheEvict注解的使用
@CacheEvict
注解中常用的五个入参如下所示:
入参 | 说明 |
---|---|
String value | 配置缓存【分区】,相当于缓存的标示,每个缓存【分区】下可以有多个 key |
String key | 配置缓存的【分区】下的具体表示,此处支持SpringEL 表达式 (后面会介绍),动态命名 |
String cacheManeger | String 选择配置类中的缓存配置对象 beanname,不选走默认 |
String condition | 注解生效条件, 支持 SpringEL 表达式 例如: #result != null 结果不为null,才进行缓存 |
boolean allEntries | 默认为false;为true时删除 value 【分区】下的全部缓存数据 |
【注意】
- 在我实际使用中,
@CacheEvict
注解如果入参为allEntries = true
时,必须放在控制层的方法上才有效,放在业务层方法上没有效果。
-
前面介绍了 SpEL 动态地计算
key
的值,其实 SpEL 还支持多种不同的写法。下面三种key
的 SpEL 都是等价的。 -
举例 1 :下面的
@CacheEvict
中的key
为#p0
。其中,#
为 SpEL 的开始标识,固定写法。而p0
表示@CacheEvict
注解所修饰的方法的第 [0] 个入参,即Long id
。@CacheEvict(value = "userCache", key = "#p0") @DeleteMapping("/{id}") public void delete(@PathVariable Long id) { userService.removeById(id); }
-
举例 2 :下面的
@CacheEvict
中的key
为#root.args[0]
。其中,#
为 SpEL 的开始标识,固定写法。而root.args[0]
表示@CacheEvict
注解所修饰的方法的第 [0] 个入参,即Long id
。@CacheEvict(value = "userCache", key = "#root.args[0]") @DeleteMapping("/{id}") public void delete(@PathVariable Long id) { userService.removeById(id); }
-
举例 3 (推荐) :下面的
@CacheEvict
中的key
为#id
。其中,#
为 SpEL 的开始标识,固定写法。而id
表示@CacheEvict
注解所修饰的方法的入参Long id
,两者的名称必须相同。@CacheEvict(value = "userCache", key = "#id") @DeleteMapping("/{id}") public void delete(@PathVariable Long id) { userService.removeById(id); }
-
以上三种
key
的 SpEL 效果都是相同的。都是以方法的入参Long id
作为缓存的 key 。
3)@Cacheable
-
@Cacheable
注解通常修饰查询的方法,但还支持条件判断参数condition
和unless
:- 只有满足
condition
中的条件时才缓存数据。 - 满足
unless
中的条件则不缓存。
- 只有满足
-
例如,可以支持当查询结果不为空的时候才缓存:
@Cacheable(value = "userCache", key = "#id", unless = "#result == null") @GetMapping("/{id}") public User getById(@PathVariable Long id) { User user = userService.getById(id); return user; }
-
举例:常见的分页查询。可以拼接 key :
@Cacheable(value = "userCache", key = "#user.id + '_' + #user.name") @GetMapping("/list") public List<User> list(User user) { LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(user.getId() != nul1, User::getId, user.getId()); queryWrapper.eq(user.getName() != nul1, User::getName, user.getName()); List<User> list = userService.list(queryWrapper); return list; }
4)总结
- Spring Cache 的使用核心在于设计如何动态地计算
key
的值,下面总结了key
的四种常用的构造方法:
key的写法 | 说明 |
---|---|
key = “#p0.id” | 把第[0]个入参的属性 id 作为key |
key = “#user.id” | 把入参 User user 的属性 id 作为key |
key = “#root.args[0].id” | 把第[0]个入参的属性 id 作为key |
key = “#result.id” | 把返回值 User user 的属性 id 作为key |
4. Spring Cache使用方式
- Spring Cache 使用只需要三步:导入Maven依赖坐标、缓存配置、添加注解。
- 在 Spring Boot 项目中,使用缓存技术只需在项目中导入相关缓存技术的依赖包,并在启动类上使用
@EnableCaching
开启缓存支持即可。 - 例如,使用 Redis 作为缓存技术,只需要导入Spring Data Redis 的Maven 坐标即可。
1)导入Maven坐标
-
打开
pom.xml
文件,确保已经添加了以下依赖的坐标:<!-- Spring Data Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!-- common-pool Redis连接池依赖 --> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!-- Spring Cache --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-cache</artifactId> </dependency> <!-- Jackson依赖 --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> </dependency>
2)修改Spring Boot配置文件
-
打开
application.yml
,添加 Spring Cache 的相关配置:spring: # 配置Redis连接 redis: host: 192.168.148.100 port: 6379 password: xsh981104 database: 0 # 设置Lettuce Redis连接池 lettuce: pool: max-active: 8 # 最大连接数 max-idle: 8 # 最大空闲连接 min-idle: 0 # 最小空闲连接 max-wait: 100ms # 等待时长 # 配置Spring Cache缓存 cache: type: redis # 设置缓存技术使用Redis redis: time-to-live: 3600000 # 设置缓存有效期为60min
3)创建Redis配置类
-
创建
src/main/java/edu/ouc/config/RedisConfig.java
。 -
Redis 配置类
RedisConfig.java
用于声明 Redis 缓存的序列化方式【JSON】,配置缓存时间等。@Configuration @EnableCaching // 开启Spring Cache缓存注解 @ConditionalOnClass(RedisOperations.class) @EnableConfigurationProperties(RedisProperties.class) public class RedisConfig extends CachingConfigurerSupport { // 实例化具体的缓存配置类 // 设置序列化方式为JSON // 设置缓存时间,单位为秒 private RedisCacheConfiguration instanceConfig(Long ttl) { // 1.创建jackson的Redis缓存序列化器 Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class); // // 2.常见的Jackson的对象映射器,并设置一些基本属性 // // 2.1 创建Jackson的对象映射器对象 // ObjectMapper objectMapper = new ObjectMapper(); // // 2.2 在序列化过程中关闭把日期时间转换成时间戳 // objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // // 2.3 注册Java的时间模块 // objectMapper.registerModule(new JavaTimeModule()); // // 2.4 禁用映射器注解 // objectMapper.configure(MapperFeature.USE_ANNOTATIONS, false); // 被弃用 // // 2.5 设置JSON序列化器,且不为空时才序列化 // objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // // 2.6 不懂 // objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, // ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); // 2.新版本Jackson推荐使用JsonMapper,替换上面的老版本的ObjectMapper JsonMapper jsonMapper = JsonMapper.builder() .configure(MapperFeature.USE_ANNOTATIONS, false) .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) .build(); jsonMapper.registerModule(new JavaTimeModule()); jsonMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); jsonMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY); // 3.为序列化器设置对象映射器 jackson2JsonRedisSerializer.setObjectMapper(jsonMapper); // 4.返回Redis缓存配置对象 return RedisCacheConfiguration.defaultCacheConfig() .entryTtl(Duration.ofSeconds(ttl)) // 设置缓存时间 .disableCachingNullValues() .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer)); } // 配置缓存Manager @Bean @Primary //同类型多个bean时,默认生效! 默认缓存时间1小时! 可以选择! public RedisCacheManager cacheManagerHour(RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration instanceConfig = instanceConfig(1 * 3600L); //缓存时间1小时 //构建缓存对象 return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(instanceConfig) .transactionAware() .build(); } //缓存24小时配置 @Bean public RedisCacheManager cacheManagerDay(RedisConnectionFactory redisConnectionFactory) { RedisCacheConfiguration instanceConfig = instanceConfig(24 * 3600L); //缓存时间24小时 //构建缓存对象 return RedisCacheManager.builder(redisConnectionFactory) .cacheDefaults(instanceConfig) .transactionAware() .build(); } }
4)启动类开启缓存注解
-
打开你的 Spring Boot 的启动类,添加打开缓存注解。重点看第 5 行代码:
@Slf4j // 日志 @SpringBootApplication // Spring Boot启动类 @ServletComponentScan // Servlet组件扫描,扫描过滤器 @EnableTransactionManagement // 开启Spring事务注解管理 @EnableCaching // 开启Spring Cache缓存 public class ReggieTakeOutApplication { public static void main(String[] args) { SpringApplication.run(ReggieTakeOutApplication.class, args); // 打印Slf4j日志 log.info("项目启动成功"); } }
5)开始编码
-
举例:黑马《瑞吉外卖》项目的套餐分页查询业务层方法,重点看第 3 行代码,直接添加注解
@Cacheable
,Spring Cache 就会先去 Redis 中查询是否有数据,如果有数据,则直接返回缓存中的数据;若没有数据,调用方法并将方法返回值放到缓存中。非常地方便好用。 -
总而言之,Spring Cache + Redis 作为缓存时,思考的重点是
key
的设计,如何设计出区分度高、可读性高的 key 才是开发者需要认真思考的。// 根据条件查询套餐集合 @Override @Cacheable(value = "setmealCache", key = "#setmeal.categoryId + '_' + #setmeal.status") public List<Setmeal> list(Setmeal setmeal) { // 1.创建查询条件封装器 LambdaQueryWrapper<Setmeal> lqw = new LambdaQueryWrapper<>(); // 2.添加查询条件:根据类别ID查询 lqw.eq(setmeal.getCategoryId() != null, Setmeal::getCategoryId, setmeal.getCategoryId()); // 3.添加查询条件:根据售卖状态查询 lqw.eq(setmeal.getStatus() != null, Setmeal::getStatus, setmeal.getStatus()); // 4.调用数据层返回套餐对象构成的集合 return this.list(lqw); }
6)Redis中缓存的数据展示
-
缓存的 Value 值都是可读性很高的、纯净的 JSON 字符串:
-
Spring Cache + Redis 自动化缓存的方案就成功搭建起来了,大家赶快用起来吧。有问题可以欢迎评论和私信。
更多推荐
所有评论(0)