专业的编程技术博客社区

网站首页 > 博客文章 正文

SpringBoot专栏:Redis缓存集成高级篇,扫了一天坑(第13讲)

baijin 2024-08-26 10:19:52 博客文章 4 ℃ 0 评论

Redis 简介

Redis是一款开源的、高性能的键-值存储(key-value store)。它常被称作是一款数据结构服务器(data structure server)。Redis的键值可以包括字符串(strings)类型,同时它还包括哈希(hashes)、列表(lists)、集合(sets)和 有序集合(sorted sets)等数据类型。 对于这些数据类型,你可以执行原子操作。例如:对字符串进行附加操作(append);递增哈希中的值;向列表中增加元素;计算集合的交集、并集与差集等...

本篇以springboot2.X为例。

pom、以及启动类那些配置本篇不再详细讲解,这里需要注意的是SpringBoot集成redis版本问题。

缓存注解

redis缓存包含以下几个注解

主要讲解下@Cacheable

一般用于查询操作,根据key查询缓存.

  1. 如果key不存在,查询db,并将结果更新到缓存中。
  2. 如果key存在,直接查询缓存中的数据
查询的例子,当第一查询的时候,redis中不存在key,会从db中查询数据,并将返回的结果插入到redis中。

核心代码

 * 运行service方法
 * @param id
 * @return
 */
 @Cacheable(cacheNames="student",key = "#id")
 public Student getStudentById(String id){
 logger.info("运行service方法,获取学生信息id{}",id);
 Student student=new Student(id,"name+"+id,18);
 return student;
 }

也就是通过controller调用的时候,如果缓存中存在此时就不再查询数据库

当我们第一次和之后的访问查看日志,发现对于同一id此时缓存是生效 的。

打印日志如下:

样例很简单,但是我们在集成的时候可能会遇到很多问题,如下是问题汇总,如果遇到,希望能提供下思路..

更多信息可以关注@架构师速成记

操作步骤开始

Controller层

?新建Student类

?Service类

这里主要讲Cacheable,其它需要深入研究的可私聊

@Cacheable 触发缓存入口

@CacheEvict 触发移除缓存

@CacahePut 更新缓存

@Caching 将多种缓存操作分组

@CacheConfig 类级别的缓存注解,允许共享缓存名称

如:

@CachePut(value = "user", key = "'user'.concat(#user.id.toString())")

@CacheEvict(value = "user", key = "'user'.concat(#id.toString())")

@Service
public class RedisService {
 Logger logger= LoggerFactory.getLogger(RedisService.class);
 @Autowired
 private RedisTemplate redisTemplate;
 /**
 * 设置缓存 key-value(包含过期时间)
 * @param key
 * @param value
 */
 public void set(String key,String value){
 ValueOperations<String, String> ops = redisTemplate.opsForValue();
 ops.set(key,value,10, TimeUnit.MINUTES);//1分钟过期
 }
 public String getValue(String key){
 ValueOperations<String, String> ops = this.redisTemplate.opsForValue();
 return ops.get(key);
 }
 /**
 * 这其中有个报错:No cache could be resolved for 'Builder[public java.util.List com.es.service.evralarm.EvrAlarmCacheService.getEvrAlarmByAccountId(java.lang.String)] caches=[] | key=''EvrAlarm-'+#accountId' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'' using resolver 'org.springframework.cache.interceptor.SimpleCacheResolver@7fbfc31a'. At least one cache should be provided per cache operation.
 * @Cacheable注解中添加cacheNames即可
 *用实体类返回报错:java.lang.String cannot be cast to com.limp.domain.Student
 * 运行service方法
 * @param id
 * @return
 */
 @Cacheable(cacheNames="student",key = "#id")
 public Student getStudentById(String id){
 logger.info("运行service方法,获取学生信息id{}",id);
 Student student=new Student(id,"name+"+id,18);
 return student;
 }
}

配置JavaConfig

  1. 注意一定要在类上加上以下两个注解:
  2. @Configuration 可理解为用spring的时候xml里面的<beans>标签
  3. @EnableCaching 注解是spring framework中的注解驱动的缓存管理功能
/**
 * 
 * @intro :
 * @auth : shinians
 * @time : 2018/12/28 0:17
 */
@Configuration
@EnableCaching
public class RedisConfiguration extends CachingConfigurerSupport {
 @Bean
 public RedisTemplate<Object, Student> redisTemplate(RedisConnectionFactory connectionFactory)
 throws UnknownHostException {
 RedisTemplate<Object,Student> template=new RedisTemplate<Object, Student>();
 template.setConnectionFactory(connectionFactory);
 Jackson2JsonRedisSerializer<Student> jackson2JsonRedisSerializer=new Jackson2JsonRedisSerializer<Student>(Student.class);
 template.setDefaultSerializer(jackson2JsonRedisSerializer);
 return template;
 }
 @Bean
 public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory){
 RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
 return RedisCacheManager
 .builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory))
 .cacheDefaults(redisCacheConfiguration).build();
 }
 @SuppressWarnings({ "unchecked", "rawtypes" })
 @Bean
 public Jackson2JsonRedisSerializer jackson2JsonRedisSerializer() {
 final Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(
 Object.class);
 final ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().build();
 objectMapper.disable(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES);
 objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
 objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
 objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
 objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
 jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
 return jackson2JsonRedisSerializer;
 }
}

注意事项:(问题汇总,总有一款能遇上)

1.实体类必须序列化(一定要形成习惯)

public class Student implements Serializable{
.....
}

否则序列化的时候就如下所示:

错误信息:

Cannot deserialize; nested exception is 
org.springframework.core.serializer.support.SerializationFailedException: Failed to 
deserialize payload. Is the byte array a result of corresponding serialization for 
DefaultDeserializer?; nested exception is java.io.InvalidClassException: 
com.limp.domain.Student; class invalid for deserialization

2.实体类如果添加了有参构造方法,最好也构建无参构造方法

public Student() {
...
 
 }

3.@Cacheable注解时候添加cacheNames(通过配置类整体配置也是ok的)

如果不添加会怎样呢?如下

这其中有个报错:No cache could be resolved for 'Builder[public java.util.List 
com.es.service.evralarm.EvrAlarmCacheService.getEvrAlarmByAccountId(java.lang.String)] 
caches=[] | key=''EvrAlarm-'+#accountId' | keyGenerator='' | cacheManager='' | cacheResolver='' | condition='' | unless='' | sync='false'' using resolver 
'org.springframework.cache.interceptor.SimpleCacheResolver@7fbfc31a'. At least one cache 
should be provided per cache operation.
 

4.springboot 从redis取缓存的时候java.lang.ClassCastException:异常(这个也是最坑人,要重视起来)

从数据库中取得的对象已经放入了redis中,而且从redis的客服端也可以查看到对应的key,第一次正常的(如访问IP/student/33),能正常的从缓存中取得并返回我需要的类;

但是过第二次访问这个方法时(这时应该是到redis里面取)抛出了java.lang.ClassCastException异常,说你的com.limp.domain.Student类,不能转为com.limp.domain.Student类。

现在汇总3中解决方案:

方案1.去掉热部署插件

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>

方案2:

在resources目录下面创建META_INF文件夹,然后创建spring-devtools.properties文件,文件加上类似下面的配置:

restart.exclude.companycommonlibs=/mycorp-common-[\w-]+.jar

restart.include.projectcommon=/mycorp-myproj-[\w-]+.jar

方案3:spring-boot-devtools模块禁用部署功能(现在这个方案测试是生效的,建议大家可以尝试下)

汇总:问题原因(部分截图)

?5.java.lang.String cannot be cast to com.limp.domain.Student

.....

6.一个类中@Cacheable标注的方法不能被本类中其他方法调用,否则缓存不起作用

正确的调用:其他类调用该方法

@Cacheable 缓存设置

?End

个人见解:项目中采用的是业务数据通过设置缓存方法(类似工具类)直接保存数据的,没有采用这种注解的方式,但是不可否认注解的方式很简便。

题外话:缓存穿透问题也需要注意

关注:更多信息关注@架构师速成记

代码下载:https://github.com/shinians/springboot-demos

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表