1. 为什么要对热点数据进行缓存?
本人做的是一个博客系统,使用redis对文章的信息进行缓存,比如说要去查询最热文章,首页的文章展示,对项目的查询接口的数据我们可以将其放入缓存中,这样子不仅可以做到查询速度快,当有大量的用户请求该接口时还能够减轻数据库的负担,提高博客浏览的速度。
2.准备工作
其实我现在设计到的是有两种方法对数据进行缓存到redis中,第一种就是使用自定义注解就是AOP思想逻辑去实现。那么本项目用的是第二种,就是之前我在瑞吉外卖学到的整合Sprting Cache框架。
3. Spring Cache简介
Spring Cache 是 Spring 提供的一整套的缓存解决方案。虽然它本身并没有提供缓存的实现,但是它提供了一整套的接口和代码规范、配置、注解等,这样它就可以整合各种缓存方案了,比如 Redis、Ehcache,我们也就不用关心操作缓存的细节。
Cache接口它包含了缓存的各种操作方式,同时还提供了各种xxxCache缓存的实现,比如 RedisCache 针对Redis,EhCacheCache 针对 EhCache,ConcurrentMapCache 针对 ConCurrentMap。
4.在本博客项目中的应用
(1)导入pom依赖
<!-- cache-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
(2)application.properties配置文件,指定redis的缓存类型
#cache 配置文件指定缓存类型
spring.cache.type=redis
(3)在启动类中开启注解
package com.jia;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.scheduling.annotation.EnableAsync;
@SpringBootApplication
@EnableCaching//开启缓存注解
public class BlogApiApplication {
public static void main(String[] args) {
SpringApplication.run(BlogApiApplication.class, args);
}
}
(4)接下来需要去添加一个CacheConfig类,设置Redis的过期时间和序列化,这段代码是配置了三个不同的Redis缓存管理器,分别用于设置缓存的过期时间为1分钟、1小时和1天。
首先,在cacheManager1Minute、cacheManager1Hour和cacheManager1Day方法中,通过调用instanceConig 方法来创建一个RedisCacheConfiguration对象。instanceConig方法接收一个ttl参数,用于设置缓存的过期时间。
接下来,在instanceConig方法中,创建了一个Jackson2JsonRedisSerializer对象,并配置了用于序列化和反序列化的ObjectMapper对象。ObjectMapper用于将对象转换为JSON字符串,并能够处理Java 8的日期时间类型。Jackson2JsonRedisSerializer则用于将对象序列化为JSON字符串,并将其存储到Redis中。
最后,在RedisCacheConfiguration对象的构建过程中,设置了缓存的过期时间、禁止缓存null值、以及使用之前创建的Jackson2JsonRedisSerializer进行值的序列化。
package com.jia.config;/**
* @author ChenJia
* @create 2023-06-17 15:12
*/
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
/**
*@ClassName CacheConfig
*@Description Cache注解类,配置了过期时间和序列化
*@Author jia
*@Date 2023/6/17 15:12
*@Version 1.0
**/
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager1Minute(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(60L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
@Bean
@Primary // 默认的,没有指定采用默认的
public RedisCacheManager cacheManager1Hour(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(3600L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
@Bean
public RedisCacheManager cacheManager1Day(RedisConnectionFactory connectionFactory){
RedisCacheConfiguration config = instanceConig(3600 * 24L);
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.transactionAware()
.build();
}
private RedisCacheConfiguration instanceConig(long ttl) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
objectMapper.registerModule(new JavaTimeModule());
//去掉各种@JsonSerialize注解的解析
objectMapper.configure(MapperFeature.USE_ANNOTATIONS,false);
//只针对非空的值进行序列化
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
//将类型序列化到属性json字符串中
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofSeconds(ttl))
.disableCachingNullValues() //禁止缓存null的值
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
}
}
(5)注解的使用,这里需要解释一下Cacheable注解:
标记在一个方法上,也可以标记在一个类上
缓存标注对象的返回结果,标注在方法上缓存该方法的返回值,标注在类上缓存该类所有方法的返回值
value缓存名称,可以有多个
key缓存的key规则,可以使用springEL表达式,默认是方法参数组合
condition缓存条件,使用springEL编写,返回true才缓存
/**
* 首页文章列表
* @param pageParams
* return 返回承担返回数据Result的类
*/
@PostMapping
//这里我们设置过期时间为1分钟,默认为一小时,统一缓存处理
@Cacheable(value = {"listArticle"}, key = "#root.methodName",cacheManager = "cacheManager1Hour")
@LogAnnotation(module = "文章",operator = "获取文章列表")
public Result listArticle(@RequestBody PageParams pageParams){
// int i =1/0;统一异常处理的测试
//对于接受的参数问题,这里是RequestBody接收
return articleService.listArticle(pageParams);
}