SpringBoot专栏 | 整合redis 高级篇(第13讲)

 

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

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

十年呵护

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值