SpringBoot 整合 Cache 并改造

SpringBoot 整合 Cache 并改造

本次改造主要实现的功能
- 缓存数据时自定义缓存时间
- 缓存失效时的模糊删除

代码实现

依赖

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

属性配置

spring:
  cache:
    type: redis
CacheConfig 缓存管理

通过 CacheConfig 配置 CacheManager ,它是缓存管理的核心组件之一,它负责管理和操作缓存,这里序列化工具与 RedisTemplate 保持一致,减少不必要的问题。

@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(5))
                .disableCachingNullValues()
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

        //RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
        CustomizedRedisCacheWriter customizedRedisCacheWriter = new CustomizedRedisCacheWriter(connectionFactory, BatchStrategies.keys());
        RedisConfigCacheManager redisConfigCacheManager = new RedisConfigCacheManager(customizedRedisCacheWriter, connectionFactory, redisCacheConfiguration);

        return redisConfigCacheManager;
    }
}
RedisConfigCacheManager 缓存管理

RedisConfigCacheManager 继承原 RedisCacheManager 对原缓存功能进行增强。在这里我们构造了自定义的 CustomizedRedisCache 对象。

public class RedisConfigCacheManager extends RedisCacheManager {

    private final RedisConnectionFactory redisConnectionFactory;
    private final RedisCacheWriter cacheWriter;

    public RedisConfigCacheManager(RedisCacheWriter cacheWriter,RedisConnectionFactory redisConnectionFactory,
                                   RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
        this.redisConnectionFactory = redisConnectionFactory;
        this.cacheWriter = cacheWriter;
    }

    @Override
    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
        return new CustomizedRedisCache(name, this.cacheWriter,redisConnectionFactory, cacheConfig);
    }

}
CustomizedRedisCache key 的模糊清理

CustomizedRedisCache 继承 RedisCache ,这里重写了缓存失效的方法

public class CustomizedRedisCache extends RedisCache {
    private static final String WILD_CARD = "*";
    private final String name;
    private final RedisCacheWriter cacheWriter;
    private final ConversionService conversionService;
    private final RedisConnectionFactory redisConnectionFactory;

    protected CustomizedRedisCache(String name, RedisCacheWriter cacheWriter, RedisConnectionFactory connectionFactory, RedisCacheConfiguration cacheConfig) {
        super(name, cacheWriter, cacheConfig);
        this.name = name;
        this.cacheWriter = cacheWriter;
        this.conversionService = cacheConfig.getConversionService();
        this.redisConnectionFactory = connectionFactory;
    }

    @Override
    public void evict(Object key) {
        if (key instanceof String) {
            String keyString = key.toString();
            //模糊清理,使用 clear 模式
            if (keyString.contains(WILD_CARD)) {
                byte[] pattern = (byte[])this.conversionService.convert(this.createCacheKey(key), byte[].class);
                this.cacheWriter.clean(this.name, pattern);
                return;
            }
        }
        super.evict(key);
    }
}
CustomizedRedisCacheWriter 缓存写入指定失效时间

自定义 CustomizedRedisCacheWriter, 重写默认的 DefaultRedisCacheWriter ,DefaultRedisCacheWriter是跟 redis 交互的底层实现类,在put 自定义实现指定失效的时间。

public class CustomizedRedisCacheWriter implements RedisCacheWriter {

    private RedisConnectionFactory connectionFactory;
    private Duration sleepTime;
    private CacheStatisticsCollector statistics;
    private BatchStrategy batchStrategy;

    CustomizedRedisCacheWriter(RedisConnectionFactory connectionFactory, BatchStrategy batchStrategy) {
        this(connectionFactory, Duration.ZERO, batchStrategy);
    }

    CustomizedRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime, BatchStrategy batchStrategy) {
        this(connectionFactory, sleepTime, CacheStatisticsCollector.none(), batchStrategy);
    }

    CustomizedRedisCacheWriter(RedisConnectionFactory connectionFactory, Duration sleepTime, CacheStatisticsCollector cacheStatisticsCollector, BatchStrategy batchStrategy) {
        Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");
        Assert.notNull(sleepTime, "SleepTime must not be null!");
        Assert.notNull(cacheStatisticsCollector, "CacheStatisticsCollector must not be null!");
        Assert.notNull(batchStrategy, "BatchStrategy must not be null!");
        this.connectionFactory = connectionFactory;
        this.sleepTime = sleepTime;
        this.statistics = cacheStatisticsCollector;
        this.batchStrategy = batchStrategy;
    }

    /**
     *  这里实现了自定义的时间
     * @param name
     * @param key
     * @param value
     * @param ttl
     */
    public void put(String name, byte[] key, byte[] value, @Nullable Duration ttl) {
        Assert.notNull(name, "Name must not be null!");
        Assert.notNull(key, "Key must not be null!");
        Assert.notNull(value, "Value must not be null!");
        execute(name, connection -> {
            int index = name.lastIndexOf("#");
            if (index  > 0){
                String expireString = name.substring(index + 1);
                long expireTime = Long.parseLong(expireString);
                connection.set(key, value, Expiration.from(expireTime, TimeUnit.MINUTES), RedisStringCommands.SetOption.upsert());
            }else if (shouldExpireWithin(ttl)) {
                connection.set(key, value, Expiration.from(ttl.toMillis(), TimeUnit.MILLISECONDS), RedisStringCommands.SetOption.upsert());
            } else {
                connection.set(key, value);
            }
            return "OK";
        });
        this.statistics.incPuts(name);
    }

使用案例

缓存
	// #后面是缓存时间,单位是分钟,缓存时间为默认值的话不用写
    @Cacheable(value = "price#30")
    public BaseResult<VO> getPrice(@RequestBody Req req) {
        Resp res = service.query(req);
        return BaseResult.success(res);
    }
    //指定 key,对于确定的key,可以不用拼接 *
    @Cacheable(value = "price#30",key = "'*' + #req.code+ '*'")
    public BaseResult<VO> getPrice(@RequestBody Req req) {
        Resp res = service.query(req);
        return BaseResult.success(res);
    } 
    //指定条件,满足条件才缓存
    @Cacheable(value = "price#30",condition = "#req.code!=null and  #req.code.length() > 0")
    public BaseResult<VO> getPrice(@RequestBody Req req) {
        Resp res = service.query(req);
        return BaseResult.success(res);
    } 
	//全部清空
    @CacheEvict(value = "price#30",allEntries = true)
    public BaseResult<VO> updatePrice(@RequestBody Req req) {
        Resp res = service.update(req);
        return BaseResult.success(res);
    }
时间处理

LocalDate LocalDateTime 序列化

    @JsonDeserialize(using = LocalDateDeserializer.class)
    @JsonSerialize(using = LocalDateSerializer.class)
    private LocalDate importDatetime;

Redis 集群模式下的范围扫描删除

redis 集群模式下范围扫描并删除的方法

      static class Scan implements BatchStrategy {
        private final int batchSize;

        Scan(int batchSize) {
            this.batchSize = batchSize;
        }

        public long cleanCache(RedisConnection connection, String name, byte[] pattern) {
            Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().count((long)this.batchSize).match(pattern).build());
            long count = 0L;
            PartitionIterator<byte[]> partitions = new PartitionIterator(cursor, this.batchSize);

            while(partitions.hasNext()) {
                List<byte[]> keys = partitions.next();
                count += (long)keys.size();
                if (keys.size() > 0) {
                    connection.del((byte[][])keys.toArray(new byte[0][]));
                }
            }

            return count;
        }
    }
其他
  • 重写 toString,做好验证,检查 toString 方法是否正常
  • 可以自定义key 的生成策略
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值