springboot集成redis使用@Cacheable存入值,但redisTemplate取值时始终为null

在SpringBoot 2.x中集成Redis并使用@Cacheable注解时,遇到缓存注解与自定义RedisUtils无法同步获取值的问题。问题源于SpringBoot自带的RedisTemplate与自定义的不一致,以及可能的序列化方式不同。解决方案是通过@Primary注解确保自定义RedisTemplate优先使用,并统一RedisTemplate与CacheManager的序列化方式,以FastJsonRedisSerializer和GenericToStringSerializer配置序列化器。

项目场景:

springboot2.x集成了redis时,使用@Cacheable注解进行缓存,同时自定义了一个RedisUtils(使用到了自定义的RedisTemplate)方便获取缓存值


问题描述:

单独使用缓存注解时可以正常存取值,单独使用RedisUtils也可以正常存取值,但通过注解存值,RedisUtils获取值时始终为null


原因分析:

有两点原因:

  1. RedisUtils中的RedisTemplate并不是配置时注入的bean(因为SpringBoot中也提供了RedisTemplate),这样可能会造成拿不到值的情况(不常见)
  2. RedisTemplate和RedisCacheManager序列化方式不一样,这个问题需要明确@Cacheable和RedisTemplate调用的方式(下面有提到),在配置中如果两个类序列化的方式不一样,那么混用时也就拿不到数据
  • @Cacheable是spring cache提供的,实现方式也是通过CacheManager进行缓存的读写
  • RedisTemplate则是springboot提供的一个关于读写redis数据的类

解决方案:

针对第一个原因,可以在自定义RedisTemplate时添加一个@Primary注解,这样就相当于告诉spring容器,如果容器中有多个RedisTemplate,这个自定义的RedisTemplate需要优先被取出,例如:

@Bean
@Primary
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
    redisTemplate.setConnectionFactory(factory);

    // 使用GenericToStringSerializer来序列化和反序列化redis的key
    GenericToStringSerializer<String> stringGenericToStringSerializer = new GenericToStringSerializer<>(String.class);
    redisTemplate.setKeySerializer(stringGenericToStringSerializer);
    redisTemplate.setHashKeySerializer(stringGenericToStringSerializer);
    redisTemplate.setStringSerializer(stringGenericToStringSerializer);

    // 使用FastJsonRedisSerializer来序列化和反序列化redis的值
    FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
    redisTemplate.setValueSerializer(fastJsonRedisSerializer);
    redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);

    return redisTemplate;
}

针对第二点原因,只要保证RedisTemplate和CacheManager序列化方式一样即可,最保险的写法如下:

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    /**
     * redisTemplate相关
     */
    @Bean("customRedisTemplate")
    @Primary
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);

        // 使用GenericToStringSerializer来序列化和反序列化redis的key
        GenericToStringSerializer<String> stringGenericToStringSerializer = new GenericToStringSerializer<>(String.class);
        redisTemplate.setKeySerializer(stringGenericToStringSerializer);
        redisTemplate.setHashKeySerializer(stringGenericToStringSerializer);
        redisTemplate.setStringSerializer(stringGenericToStringSerializer);

        // 使用FastJsonRedisSerializer来序列化和反序列化redis的值
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);

        return redisTemplate;
    }

    /**
     * 选择redis作为默认缓存工具
     */
    @Bean("redisCacheManager")
    @Primary
    @DependsOn("customRedisTemplate")
    public CacheManager cacheManager(RedisTemplate<String, Object> redisTemplate) {
        return RedisCacheManager.RedisCacheManagerBuilder
                .fromConnectionFactory(redisTemplate.getConnectionFactory())
                // 设置缓存默认永不过期
                .cacheDefaults(
                        RedisCacheConfiguration.defaultCacheConfig()
                                // 不缓存null(需要与unless = "#result == null"共同使用)
                                .disableCachingNullValues()
                                .serializeKeysWith(
                                        RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getStringSerializer()))
                                .serializeValuesWith(
                                        RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer())))
                // 配置同步修改或删除 put/evict
                .transactionAware()
                .build();
    }
}
在Spring Boot 3.0使用Redis的`@Cacheable`注解对应多个参数,且Redis采用Lettuce模式`key`显示为`null`,可尝试以下解决方案: #### 1. 明确指定`key`表达式 `@Cacheable`注解的`key`属性支持SpEL(Spring Expression Language)表达式,可通过该表达式根据方法参数生成合适的`key`。 ```java import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; @Service public class UserService { @Cacheable(value = "users", key = "#id + '_' + #name") public User getUserByIdAndName(Long id, String name) { // 从数据库查询 return userRepository.findByIdAndName(id, name); } } ``` 上述代码中,`key`表达式`#id + '_' + #name`将方法的两个参数`id`和`name`组合成一个唯一的`key`。 #### 2. 使用`keyGenerator` 若`key`生成逻辑较为复杂,可自定义`keyGenerator`。 ```java import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.interceptor.KeyGenerator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Service; import java.lang.reflect.Method; @Configuration public class CacheConfig { @Bean public KeyGenerator customKeyGenerator() { return new KeyGenerator() { @Override public Object generate(Object target, Method method, Object... params) { StringBuilder key = new StringBuilder(); key.append(method.getName()); for (Object param : params) { key.append("_").append(param.toString()); } return key.toString(); } }; } } @Service public class UserService { @Cacheable(value = "users", keyGenerator = "customKeyGenerator") public User getUserByIdAndName(Long id, String name) { // 从数据库查询 return userRepository.findByIdAndName(id, name); } } ``` 上述代码中,自定义的`KeyGenerator`将方法名和所有参数组合成一个`key`。 #### 3. 检查序列化配置 确保Redis的序列化配置正确,特别是`key`的序列化器。 ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } } ``` 上述代码中,将`key`的序列化器设置为`StringRedisSerializer`,确保`key`能正确序列化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值