一、核心流程

  1. 引入依赖
  2. 创建RedisConfig配置文件
  3. 使用Redis缓存数据

配置Redis的序列化是为了解决程序中的数据转化成Redis二进制数据后无法直观查看数据信息的问题。

二、代码实战

2.1 引入依赖

spring-boot-starter-data-redis默认会使用lettuce,本次使用的是Jedis,所以依赖中我们要屏蔽掉lettuce而改用jedis。

Springboot会帮我们自动装配Redis,自动生成RedisConnectionFactory、RedisTemplate、StringRedisTemplate等常用的Redis对象。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <version>2.7.5</version>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

在spring-data-redis中,RedisConnection通过RedisConnectionFactory创建,Spring为了简化以上操作,做出了简化,并诞生了RedisTemplate

2.2 创建RedisConfig配置文件

因为Redis的数据以二进制的方式存储,与Java数据不相同,所以直接存入Java数据到Redis中会导致你在使用RedisCli查看数据时,是我们无法看懂的二进制序列化数据,为了解决这个问题,我们需要对Redis配置序列化的方式。

package com.example.demo.config.redis;
/**
 * @Author : HuangJiajian
 * @create 2022/10/24 9:07
 */

@Configuration
public class RedisConfig {
    /**
     * @Author HJJ
     * @Date 2022-12-30 15:49
     * @Params
     * @Return
     * @Description 注入CacheManager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        return RedisCacheManager.builder(factory)
                .cacheDefaults(defaultCacheConfig())
                .transactionAware()
                .build();
    }

    /**
     * @Author HJJ
     * @Date 2022-12-30 15:48
     * @Params
     * @Return
     * @Description 配置通过@Cacheable注解缓存的序列化方式以及过期时间
     */
    private RedisCacheConfiguration defaultCacheConfig() {
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
        serializer.setObjectMapper(mapper);
        return RedisCacheConfiguration.defaultCacheConfig()
                // 20秒过期
                .entryTtl(Duration.ofSeconds(20))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(serializer))
                .disableCachingNullValues();
    }
}

2.3 使用Redis缓存数据

在Controller层中,使用@Cacheable注解来缓存return的数据。

@Cacheable(value, key)中,value是属性名称,key是标识,在RedisCli中应该通过get value::key的形式读取数据,如get User::Jackget User::John

package com.example.demo.controller;

/**
 * @Author : HuangJiajian
 * @create 2022/12/2 10:59
 */
@RestController
@RequestMapping("/cache")
public class TestRedis {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @GetMapping("/str")
    @Cacheable(key = "#str", value = "Str")
    public String cacheStr(String str) {
        System.out.println("缓存str(第二次同参数时,不应该出现这一打印信息):" + str);
        return cache;
    }
    
    @GetMapping("/user")
    @Cacheable(key = "#username", value = "User")
    public AjaxResult cacheUser(String username) {
        UserEntity userEntity = new UserEntity(u, "123456");
        System.out.println("缓存username(第二次同参数时,不应该出现这一打印信息):" + username);
        return new AjaxResult(AjaxResult.CODE.SUCCESS, "success", userEntity);
    }
}

验证缓存

发送发送2次GET请求/cache/str,参数都是?str=value1,结果如下:

第一次请求:

控制台打印出”缓存str(第二次同参数时,不应该出现这一打印信息):value1“

浏览器获取到数据“value1”

RedisCli运行get Str::value1打印出”value1″

第二次请求:

控制台不打印

浏览器获取到数据“value1”

RedisCli运行get Str::value1打印出”value1″

三、拓展知识

3.1 Redis事务

事务处理简单来讲就是“要么是0,要么是1”。常见于电商系统,如商品在用户提交的订单后库存便减少,但若用户取消支付或支付失败,就需要将库存数据回滚。Redis也具有部分事务处理的功能。

Redis的事务处理包含三个部分:watch(监控)…multi(开始)…exec(执行)

@GetMapping("/multi")
public String testMulti(){
    stringRedisTemplate.opsForValue().set("key1", "value1");
    List list = (List) stringRedisTemplate.execute(new SessionCallback<Object>() {
        @Override
        public Object execute(RedisOperations ops) throws DataAccessException {
            ops.watch("key1");
            ops.multi();
            ops.opsForValue().set("key2", "value2");
            // 还未执行事务,所以数据是空的
            System.out.println(ops.opsForValue().get("key2")); // null
            // 情况1:在multi和exec之间打断点,然后使用reids-cli修改key1的值,exec之后Redis会监听到key1已经发生改变,故会回滚事务。
            // 情况2:key1增操作错误,但Redis仅存在队列中,直到exec时才会报错,应避免
            // ops.opsForValue().increment("key1", 1);
            ops.exec();
            return null;
        }
    });
    System.out.println(list);
    return "end";
}

四、问题记录

4.1 @Cacheable注解失效

@Cacheable的原理是SpringAOP,很多@Cacheable失效的原因都是因为RedisConfig的初始化在其使用位置之后,所以无法执行AOP拦截,导致@Cacheable注解失效。这个可以通过使用@Autowired StringRedisTemplate stringRedisTemplate;实现Redis缓存,但这样并不会用上RedisConfig中的配置,所以需要在函数中自己配置序列化方式和过期时间等。

还有一种是@Cacheable方法A(),然后使用方法B()去调用方法A,我知道你想要让B调用A时,返回A在Redis中的缓存数据,但这样也是无法做到的。

参考文献

  1. 10分钟拿下 HashMap
  2. 深入浅出Spring Boot 2.x
  3. RedisTemplate配置的jackson.ObjectMapper里的一个enableDefaultTyping方法过期解决

转自:
https://blog.csdn.net/qq_41297145/article/details/128497859