@ CacheEvict + redis Realizes Batch Cache Deletion

  • 2021-11-24 01:46:33
  • OfStack

Directory @ CacheEvict + redis Bulk Delete Cache 1. @ Cacheable Annotation 2. @ CacheEvict Annotation 3. Bulk Delete Cache 4. Code @ CacheEvict Clear All Caches Under Specification

@ CacheEvict + redis Bulk Delete Cache

1. @ Cacheable notes

Add a cache.


    /**
     * @Cacheable
     * 将方法的运行结果进行缓存;以后再要相同的数据,直接从缓存中获取,不用调用方法;
     * CacheManager管理多个Cache组件,对缓存的真正CRUD操作在Cache组件中,每1个缓存组件有自己唯11个名字;
     *
     *
     * 原理:
     *   1、自动配置类;CacheAutoConfiguration
     *   2、缓存的配置类
     *   org.springframework.boot.autoconfigure.cache.GenericCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.JCacheCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.EhCacheCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.HazelcastCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.InfinispanCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.CouchbaseCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.CaffeineCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.GuavaCacheConfiguration
     *   org.springframework.boot.autoconfigure.cache.SimpleCacheConfiguration【默认】
     *   org.springframework.boot.autoconfigure.cache.NoOpCacheConfiguration
     *   3、哪个配置类默认生效:SimpleCacheConfiguration;
     *
     *   4、给容器中注册了1个CacheManager:ConcurrentMapCacheManager
     *   5、可以获取和创建ConcurrentMapCache类型的缓存组件;他的作用将数据保存在ConcurrentMap中;
     *
     *   运行流程:
     *   @Cacheable:
     *   1、方法运行之前,先去查询Cache(缓存组件),按照cacheNames指定的名字获取;
     *      (CacheManager先获取相应的缓存),第1次获取缓存如果没有Cache组件会自动创建。
     *   2、去Cache中查找缓存的内容,使用1个key,默认就是方法的参数;
     *      key是按照某种策略生成的;默认是使用keyGenerator生成的,默认使用SimpleKeyGenerator生成key;
     *          SimpleKeyGenerator生成key的默认策略;
     *                  如果没有参数;key=new SimpleKey();
     *                  如果有1个参数:key=参数的值
     *                  如果有多个参数:key=new SimpleKey(params);
     *   3、没有查到缓存就调用目标方法;
     *   4、将目标方法返回的结果,放进缓存中
     *
     *   @Cacheable标注的方法执行之前先来检查缓存中有没有这个数据,默认按照参数的值作为key去查询缓存,
     *   如果没有就运行方法并将结果放入缓存;以后再来调用就可以直接使用缓存中的数据;
     *
     *   核心:
     *      1)、使用CacheManager【ConcurrentMapCacheManager】按照名字得到Cache【ConcurrentMapCache】组件
     *      2)、key使用keyGenerator生成的,默认是SimpleKeyGenerator
     *
     *
     *   几个属性:
     *      cacheNames/value:指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
     *
     *      key:缓存数据使用的key;可以用它来指定。默认是使用方法参数的值  1-方法的返回值
     *              编写SpEL; #i d;参数id的值   #a0  #p0  #root.args[0]
     *              getEmp[2]
     *
     *      keyGenerator:key的生成器;可以自己指定key的生成器的组件id
     *              key/keyGenerator:2选1使用;
     *
     *
     *      cacheManager:指定缓存管理器;或者cacheResolver指定获取解析器
     *
     *      condition:指定符合条件的情况下才缓存;
     *              ,condition = "#id>0"
     *          condition = "#a0>1":第1个参数的值》1的时候才进行缓存
     *
     *      unless:否定缓存;当unless指定的条件为true,方法的返回值就不会被缓存;可以获取到结果进行判断
     *              unless = "#result == null"
     *              unless = "#a0==2":如果第1个参数的值是2,结果不缓存;
     *      sync:是否使用异步模式
     *
     */

2. @ CacheEvict Notes

Clear the cache.

cacheNames/value: 指定缓存组件的名字;将方法的返回结果放在哪个缓存中,是数组的方式,可以指定多个缓存;
key 缓存数据使用的key
allEntries 是否清除这个缓存中所有的数据。true:是;false:不是
beforeInvocation 缓存的清除是否在方法之前执行,默认代表缓存清除操作是在方法执行之后执行;如果出现异常缓存就不会清除。true:是;false:不是

3. Bulk Delete Cache

In practical applications, some caches have the same prefix or suffix. When the database is updated, it is necessary to delete a cache of type 1 (that is, the same prefix).

However, @ CacheEvict can only delete key individually, and does not support fuzzy matching deletion.

Solution: Use redis + @ CacheEvict to solve the problem.

@ CacheEvict actually calls the evict method of RedisCache to delete the cache. The following is part of the code for RedisCache. As you can see, the evict method does not support fuzzy matching, while the clear method does.


  
    /*
	 * (non-Javadoc)
	 * @see org.springframework.cache.Cache#evict(java.lang.Object)
	 */
	@Override
	public void evict(Object key) {
		cacheWriter.remove(name, createAndConvertCacheKey(key));
	}
 
	/*
	 * (non-Javadoc)
	 * @see org.springframework.cache.Cache#clear()
	 */
	@Override
	public void clear() {
 
		byte[] pattern = conversionService.convert(createCacheKey("*"), byte[].class);
		cacheWriter.clean(name, pattern);
	}

Therefore, the problem of fuzzy match deletion can be solved only by overriding the evict method of RedisCache.

4. Code

4.1 Customizing RedisCache:


public class CustomizedRedisCache extends RedisCache {
    private static final String WILD_CARD = "*"; 
    private final String name;
    private final RedisCacheWriter cacheWriter;
    private final ConversionService conversionService; 
    protected CustomizedRedisCache(String name, RedisCacheWriter cacheWriter, RedisCacheConfiguration cacheConfig) {
        super(name, cacheWriter, cacheConfig);
        this.name = name;
        this.cacheWriter = cacheWriter;
        this.conversionService = cacheConfig.getConversionService();
    }
 
    @Override
    public void evict(Object key) {
        if (key instanceof String) {
            String keyString = key.toString();
            if (keyString.endsWith(WILD_CARD)) {
                evictLikeSuffix(keyString);
                return;
            }
            if (keyString.startsWith(WILD_CARD)) {
                evictLikePrefix(keyString);
                return;
            }
        }
        super.evict(key);
    }
 
    /**
     *  Prefix matching 
     *
     * @param key
     */
    public void evictLikePrefix(String key) {
        byte[] pattern = this.conversionService.convert(this.createCacheKey(key), byte[].class);
        this.cacheWriter.clean(this.name, pattern);
    }
 
    /**
     *  Suffix matching 
     *
     * @param key
     */
    public void evictLikeSuffix(String key) {
        byte[] pattern = this.conversionService.convert(this.createCacheKey(key), byte[].class);
        this.cacheWriter.clean(this.name, pattern);
    }  
}

4.2 Rewrite RedisCacheManager with a custom RedisCache:


public class CustomizedRedisCacheManager extends RedisCacheManager { 
    private final RedisCacheWriter cacheWriter;
    private final RedisCacheConfiguration defaultCacheConfig;
    private final Map<String, RedisCacheConfiguration> initialCaches = new LinkedHashMap<>();
    private boolean enableTransactions; 
 
    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
        super(cacheWriter, defaultCacheConfiguration);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }
 
    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheNames);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }
 
    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {
        super(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation, initialCacheNames);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }
 
    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }
 
    public CustomizedRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {
        super(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, allowInFlightCacheCreation);
        this.cacheWriter = cacheWriter;
        this.defaultCacheConfig = defaultCacheConfiguration;
    }
 
    /**
     *  This construction method is the most important 
     **/
    public CustomizedRedisCacheManager(RedisConnectionFactory redisConnectionFactory, RedisCacheConfiguration cacheConfiguration) {
        this(RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),cacheConfiguration);
    }
 
    /**
     *  Override parent class creation RedisCache
     */
    @Override
    protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {
        return new CustomizedRedisCache(name, cacheWriter, cacheConfig != null ? cacheConfig : defaultCacheConfig);
    }
 
    @Override
    public Map<String, RedisCacheConfiguration> getCacheConfigurations() {
        Map<String, RedisCacheConfiguration> configurationMap = new HashMap<>(getCacheNames().size());
        getCacheNames().forEach(it -> {
            RedisCache cache = CustomizedRedisCache.class.cast(lookupCache(it));
            configurationMap.put(it, cache != null ? cache.getCacheConfiguration() : null);
        });
        return Collections.unmodifiableMap(configurationMap);
    }
}

4.3 Using Custom CacheManager in RedisTemplateConfig


@Bean
 public CacheManager oneDayCacheManager(RedisConnectionFactory factory) {
  RedisSerializer<String> redisSerializer = new StringRedisSerializer();
  Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
 
  // Solve the problem of abnormal query cache conversion 
  ObjectMapper om = new ObjectMapper();
  om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
  om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
  jackson2JsonRedisSerializer.setObjectMapper(om);
 
  //  Configure serialization (solve the problem of garbled code) 
  RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
    // 1 Day cache expiration 
    .entryTtl(Duration.ofDays(1))
    .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
    .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
    .computePrefixWith(name -> name + ":")
    .disableCachingNullValues();
  return new CustomizedRedisCacheManager(factory, config);
 }

4.4 Using @ CacheEvict Fuzzy Matching Deletion on Code Methods


@Cacheable(value = "current_group", cacheManager = "oneDayCacheManager",
            key = "#currentAttendanceGroup.getId() + ':' + args[1]", unless = "#result eq null")
    public String getCacheAttendanceId(CurrentAttendanceGroupDO currentAttendanceGroup, String dateStr) {
        //  Method body 
    } 
 
    @CacheEvict(value = "current_group", key = "#currentAttendanceGroup.getId() + ':' + '*'", beforeInvocation = true)
    public void deleteCacheAttendanceId(CurrentAttendanceGroupDO currentAttendanceGroup) { 
    }

Note: If there are more than one CacheManager in the RedisTemplateConfig, you can use the @ Primary annotation to annotate the CacheManager in effect by default

@ CacheEvict clears all caches under the specified


@CacheEvict(cacheNames = "parts:grid",allEntries = true) 

This annotation clears all caches under part: grid

@ CacheEvict requires that one or more caches be specified so that they are all affected.

In addition, an additional parameter allEntries is provided. Indicates whether all elements in the cache need to be cleared.

The default is false, which means it is not required. When allEntries is specified as true, Spring Cache ignores the specified key.

Sometimes we need to clear all the elements under Cache1.


Related articles: