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

配置

CacheManager 类

直接上代码

key前缀配置

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

spring:
	cache:
		redis:
			key-prefix: xxx
			


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);

    }

}
 

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);
        }
    }


}

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();
            }
        }
    }

@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) {
	}

@CacheEvict

	@Override
	//key不能直接用星号,要加单引号。 否则Problem parsing left operand
	@CacheEvict(value = {"queryHotCoursesV3","findHotCoursesTempV2"}
			,key = "'*'"
	)
	public void flushCache() {
		mystudyMapper.flushCache();
	}

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐