• spring缓存注解@Cacheable和@CacheEvict,设置过期时间和批量模糊删除


    spring缓存注解@Cacheable和@CacheEvict,设置过期时间和批量模糊删除

    配置

    CacheManager 类

    直接上代码

    key前缀配置

    spring-data-redis高版本的话, 直接在yaml配置即可,但是我的是2.1.18,不知道为什么没生效,看了下两个版本设置前缀的方法也不一样,应该是版本问题。
    或者直接在配置类中设置,如下配置computePrefixWith() 如果有问题,打断点看RedisCache的createCacheKey方法

    spring:
    	cache:
    		redis:
    			key-prefix: xxx
    			
    
    • 1
    • 2
    • 3
    • 4
    • 5
    
    
    import com.fasterxml.jackson.annotation.JsonAutoDetect;
    import com.fasterxml.jackson.annotation.JsonInclude;
    import com.fasterxml.jackson.annotation.PropertyAccessor;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.cache.CacheManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.data.redis.cache.CacheKeyPrefix;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheWriter;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
    import org.springframework.data.redis.serializer.RedisSerializationContext;
    import org.springframework.data.redis.serializer.RedisSerializer;
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    
    import java.time.Duration;
    import java.time.temporal.ChronoUnit;
    
    
    @Configuration
    public class CacheConfig {
        /**
         * 最终调用 org.springframework.data.redis.cache.RedisCacheConfiguration#getKeyPrefixFor(java.lang.String)
         */
        private static final CacheKeyPrefix DEFAULT_CACHE_KEY_PREFIX = cacheName -> "edu:service_spring:"+cacheName+":";
    
    
        @Bean
        public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
    
            ObjectMapper om = new ObjectMapper();
    
            RedisSerializer redisSerializer = new StringRedisSerializer();
            Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(
                    Object.class);
            // 解决查询缓存转换异常的问题
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
            om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
            //非null才序列化
            om.setDefaultPropertyInclusion(JsonInclude.Include.NON_NULL);
            jackson2JsonRedisSerializer.setObjectMapper(om);
            // 配置序列化(解决乱码的问题)
            RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                    .computePrefixWith(DEFAULT_CACHE_KEY_PREFIX)
                    .entryTtl(Duration.of(1, ChronoUnit.DAYS))
                    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer));
            RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory);
    
            return new RedisConfigCacheManager(cacheWriter, redisConnectionFactory,config);
    
        }
    
    }
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58

    RedisCache配置

    RedisCache不能针对具体每个key进行配置过期时间, 所以改造. 通过@Cacheable里的value, 分割#号后面的作为过期时间

    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.data.redis.cache.RedisCache;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheManager;
    import org.springframework.data.redis.cache.RedisCacheWriter;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    
    import java.time.Duration;
    
    /**
     * redis 配置类
     */
    @Slf4j
    public class RedisConfigCacheManager extends RedisCacheManager {
    
        private final RedisConnectionFactory redisConnectionFactory;
        private final RedisCacheWriter redisCacheWriter;
    
        public RedisConfigCacheManager(RedisCacheWriter cacheWriter,RedisConnectionFactory redisConnectionFactory,
                                       RedisCacheConfiguration defaultCacheConfiguration) {
            super(cacheWriter, defaultCacheConfiguration);
            this.redisConnectionFactory = redisConnectionFactory;
            this.redisCacheWriter = cacheWriter;
        }
    
    
    
    
        @Override
        protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
            final int lastIndexOf = StringUtils.lastIndexOf(name, '#');
            if (lastIndexOf > -1) {
                final String ttl = StringUtils.substring(name, lastIndexOf + 1);
                final Duration duration = Duration.ofSeconds(Long.parseLong(ttl));
                cacheConfig = cacheConfig.entryTtl(duration);
                //修改缓存key和value值的序列化方式
                final String cacheName = StringUtils.substring(name, 0, lastIndexOf);
                return new CustomizedRedisCache(cacheName,this.redisCacheWriter,this.redisConnectionFactory, cacheConfig);
            }else{
                return new CustomizedRedisCache(name,this.redisCacheWriter,this.redisConnectionFactory, cacheConfig);
            }
        }
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    RedisCache

    模糊匹配删除缓存

    @CacheEvict需要注意几点:

    1. 默认是不能模糊匹配的, @CacheEvict最终调用的就是RedisCache的evict方法,所以我们重写这个方法
    2. 批量清楚RedisCache采用的是keys命令匹配, 一般这个命令生产不能用, 换成scan
    3. evict方法入参的key就是@CacheEvict的key, 不是完整的redisKey, 需要调用createCacheKey来获得完整 的key
    4. @CacheEvict的key,不能直接写*号, 因为会解析el表达式报错, 要加上单引号
    
    import org.springframework.core.convert.ConversionService;
    import org.springframework.data.redis.cache.RedisCache;
    import org.springframework.data.redis.cache.RedisCacheConfiguration;
    import org.springframework.data.redis.cache.RedisCacheWriter;
    import org.springframework.data.redis.connection.RedisConnection;
    import org.springframework.data.redis.connection.RedisConnectionFactory;
    import org.springframework.data.redis.core.Cursor;
    import org.springframework.data.redis.core.ScanOptions;
    
    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;
        }
    
        /**
         * 重写evict失效方法,即@CacleEvict注解调用的
         * @param key the key whose mapping is to be removed from the cache
         */
        @Override
        public void evict(Object key) {
            if (key instanceof String) {
                String keyString = key.toString();
                if (keyString.endsWith(WILD_CARD)) {
                    //转化为真正的rediskey,即加上前缀
                    String cacheKey = super.createCacheKey(key);
                    evictLikeSuffix(cacheKey);
                    return;
                }
            }
            super.evict(key);
        }
    
    
        /**
         * 后缀匹配
         *
         * @param key
         */
        public void evictLikeSuffix(String key) {
            //用scan替代keys
            RedisConnection connection = null;
            try {
                connection = this.redisConnectionFactory.getConnection();
                Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder().match(key).count(200).build());
                while (cursor.hasNext()) {
                    connection.del(cursor.next());
                }
            } finally {
                if (connection != null) {
                    connection.close();
                }
            }
        }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65

    @Cacheable

    最终完整的key是
    zgd:service_spring:queryHotCoursesV3:xxxx(dto序列化string)

    	@Cacheable(value = "queryHotCoursesV3#3600"
    			, condition =
    			"#dto.startPage <= 5 and #dto.pageSize <= 20 " +
    					"and (#dto.keyWords == null or #dto.keyWords == '')"
    	 )
    	public PageVO<CoursesVO> queryHotCoursesV3(CoursePageParamReq dto) {
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    @CacheEvict

    	@Override
    	//key不能直接用星号,要加单引号。 否则Problem parsing left operand
    	@CacheEvict(value = {"queryHotCoursesV3","findHotCoursesTempV2"}
    			,key = "'*'"
    	)
    	public void flushCache() {
    		mystudyMapper.flushCache();
    	}
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
  • 相关阅读:
    LeetCode 376. 摆动序列
    【C语言】编译和链接
    【ViT(Vision Transformer)】(二) 阅读笔记
    微信小程序实现蓝牙开门前后端项目(一)
    安装VCenter6.7【VCSA6.7(vCenter Server Appliance 6.7) 】
    代码混淆界面介绍
    LeetCode刷题day48|198.打家劫舍、213.打家劫舍Ⅱ、337.打家劫舍Ⅲ
    基于Highcharts平台的桑基图(Sankey diagram)绘制
    origin软件怎么找某条曲线斜率最大值处?
    msvcp140.dll丢失的解决方法win10_简单方便一点的方法推荐
  • 原文地址:https://blog.csdn.net/zzzgd_666/article/details/126769705