SpringBoot2.2+版本整合redis(template/cacheManager/过期监听)

一、说明

在SpringBoot项目开发过程中,我们经常会有用到缓存的需求,这时候整合使用Redis无疑是很好的选择。SpringBoot官方就有很多整合NoSQL的starter库,方便我们集成开发。值得一提的是,SpringBoot版本1.+和2.+关于配置Redis有比较大的区别,网络上的很多旧版的资料都已不适用于高版本的SpringBoot,因此本文记录了基于SpringBoot2.2.8Release版本的整合。

二、整合配置

(一)添加pom依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
</dependencies>

(二)新建Redis配置类

说明:

  1. redisTemplate:最基础的组件,重写它主要是为了自定义序列化策略
  2. cacheManager:SpringBoot缓存体系中的缓存管理器
  3. redisMessageListenerContainer:Redis消息监听器,能实现消息订阅、过期通知等

1. RedisConfiguration

@EnableCaching
@Configuration
public class RedisConfiguration {

	/** 默认的java8日期时间格式 */
    private static final String    DEFAULT_DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
    /** 默认的java8日期格式 */
    private static final String    DEFAULT_DATE_FORMAT      = "yyyy-MM-dd";
    /** 默认的java8时间格式 */
    private static final String    DEFAULT_TIME_FORMAT      = "HH:mm:ss";

	/**
     * Redis访问模板
     */
    @Bean
    @SuppressWarnings("all")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key都采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        template.setHashKeySerializer(stringRedisSerializer);
        // value的序列化方式采用jackson
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        // 解决查询缓存转换异常的问题
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
        // 添加java8时间相关序列化/反序列化处理
        JavaTimeModule javaTimeModule = new JavaTimeModule();
        javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
        javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_TIME_FORMAT)));
        javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ofPattern(DEFAULT_DATE_FORMAT)));
        javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ofPattern(DEFAULT_TIME_FORMAT)));
        objectMapper.registerModule(javaTimeModule);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        template.setValueSerializer(jackson2JsonRedisSerializer);
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();

        return template;
    }

	/**
     * 缓存管理器
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, RedisTemplate redisTemplate) {

        // 配置默认过期时间和序列化机制
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofSeconds(60L))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(redisTemplate.getValueSerializer()));

        // 自定义缓存空间应用不同配置,可以配置不同的过期时间
        Set<String> cacheNames =  new HashSet<>();
        Map<String, RedisCacheConfiguration> configMap = new HashMap<>(RedisCacheSpaceExpireConfig.EXPIRE_TIME_MAP.size());
        for (Map.Entry<String, Integer> entry : RedisCacheSpaceExpireConfig.EXPIRE_TIME_MAP.entrySet()) {
            String cacheName = entry.getKey();
            Integer expireMinute = entry.getValue();
            cacheNames.add(cacheName);
            configMap.put(cacheName, defaultCacheConfig.entryTtl(Duration.ofMinutes(expireMinute)));
        }

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(defaultCacheConfig)
                .initialCacheNames(cacheNames)
                .withInitialCacheConfigurations(configMap)
                .build();

    }

    /**
     * 消息监听器
     */
    @Bean
    RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        return container;
    }

}

2. RedisCacheSpaceExpireConfig(缓存空间过期时间配置类)

public class RedisCacheSpaceExpireConfig {

    public static final String MINUTE_1 = "MINUTE_1";
    public static final String MINUTE_3 = "MINUTE_3";
    public static final String MINUTE_5 = "MINUTE_5";
    public static final String MINUTE_15 = "MINUTE_10";
    public static final String MINUTE_30 = "MINUTE_30";

    public static final String HOUR_1 = "HOUR_1";
    public static final String HOUR_2 = "HOUR_2";
    public static final String HOUR_6 = "HOUR_6";
    public static final String HOUR_12 = "HOUR_12";

    public static final String DAY_1 = "DAY_1";
    public static final String DAY_3 = "DAY_3";
    public static final String DAY_10 = "DAY_10";
    public static final String DAY_30 = "DAY_30";

    /**
     * 过期时间键值对 k-v : 名称-过期时间(分钟)
     */
    public static final HashMap<String, Integer> EXPIRE_TIME_MAP = new HashMap<String, Integer>(){
        {
            put(MINUTE_1, 1);
            put(MINUTE_3, 3);
            put(MINUTE_5, 5);
            put(MINUTE_15, 15);
            put(MINUTE_30, 30);

            put(HOUR_1, 60);
            put(HOUR_2, 2 * 60);
            put(HOUR_6, 6 * 60);
            put(HOUR_12, 12 * 60);

            put(DAY_1, 60 * 24);
            put(DAY_3, 60 * 24 * 3);
            put(DAY_10, 60 * 24 * 10);
            put(DAY_30, 60 * 24 * 30);

        }
    };

}

三、使用示范

(一)简单读写

@RestController
@RequestMapping("demo")
public class DemoController {
	
	@Resource
    private RedisTemplate redisTemplate;

	@PostMapping("/simple-string")
    public String testExpire(String message) {
        redisTemplate.opsForValue().set("demo", message);
        return "success";
    }

	@GetMapping("/simple-string")
    public String testExpire(String message) {
        redisTemplate.opsForValue().set("demo", message);
        return "success";
    }

}

(二)缓存

以Mybatis的Mapper方法为例:Member类为实力类,读者视自己情况修改。RedisCacheSpaceExpireConfig为前文配置缓存空间过期时间的配置类。
SpringBoot的缓存除了加载DAO层上,还能加载各种层的方法上,视业务情况使用即可。


    /**
     * 根据主键ID和商家号查找会员:联合主键进行缓存,缓存时长为5分钟
     *
     * @param id         主键
     * @param merchantId 商家号
     * @return 会员
     */
    @Cacheable(cacheNames = RedisCacheSpaceExpireConfig.MINUTE_5, key = "'member:'.concat(#merchantId).concat(#id)")
    Member selectByIdAndMerchantId(@Param("id") Long id, @Param("merchantId") Long merchantId);

	/**
     * 根据主键ID和商家号修改会员:删除联合主键缓存内容
     *
     * @param id         主键
     * @param merchantId 商家号
     * @return 会员
     */
    @CacheEvict(cacheNames = RedisCacheSpaceExpireConfig.MINUTE_5, key = "'member:'.concat(#merchantId).concat(#id)")
    void updateByIdAndMerchantId(@Param("id") Long id, @Param("merchantId") Long merchantId, @Param("member") Member member);

说明:关于SpringBoot的缓存,本文不拓展讲,感兴趣的读者可自行查阅相关资料。缓存注解中的key支持SPEL表达式:
在这里插入图片描述

(三)过期事件监听

@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {

    public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
        super(listenerContainer);
    }

    @Resource
    private RedisTemplate redisTemplate;
    
    /**
     * 针对redis数据失效事件,进行数据处理
     *
     * @param message redis数据的key值
     * @param pattern 
     */
    @Override
    public void onMessage(Message message, byte[] pattern) {
        String redisKey = message.toString();
		// 取出我们自定义的业务标识前缀,可以使用枚举类辅助
        String prefixStr = redisKey.substring(0, redisKey.indexOf(":"));
        if (StrUtil.isBlank(prefixStr)) {
            return;
        }
        // 以不同的锁Key作为redis锁,并设置过期时间3分钟,确保多节点并发时仅执行一次任务,且锁会自动释放
        if (redisTemplate.opsForValue().setIfAbsent("lock:" + redisKey, redisKey, 3, TimeUnit.MINUTES)) {
            // 业务信息:可以是表ID、数据编码等
            String businessKey = redisKey.substring(prefixStr.length() + 1);
            switch (prefixStr) {
                case "order":
                	System.out.println("处理订单,订单号:" + businessKey );
                    break;
                case "stock":
                    System.out.println("处理库存,仓库号:" + businessKey );
                    break;
                default:
                    break;
            }
            // 释放redis锁
            redisTemplate.delete(redisKey);
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值