Search code examples
javaspringcachingspring-elevict

Cache evict on one of multiple keys


In my application I have multiple cacheable methods with multiple keys:

@Cacheable(cacheNames = "valueCodes", key = "{#value, #fieldId, #projectId}")
@Cacheable(cacheNames = "fieldNames", key = "{#field, #value, #projectId}")
@Cacheable(cacheNames = "qi", key = "{#langCode, #question, #projectId}")
@Cacheable(cacheNames = "fieldCodes", key = "{#name, #projectId}")

Now I want a cachevict method which cleares all the caches where only the #projectId key, which is a UUID, matches:

@CacheEvict(value = {"valueCodes", "fieldCodes", "qi"; "fieldCodes"}, key = "#projectId")

I've read in this article that this is not possible and that

Only the evict annotation's key regex matching more than one element in each of the cacheNames

I'm not really sure what they mean by that, but I guess it has something to do with using regex in SpEL.

So I started thinking about concatinating my keys into one key:

@Cacheable(cacheNames="cahceName", key="concat(#projectId).concat(#otherKey)")

and using regex to match all keys with the projectId followed by a wildcard. But I couldn't really find a way to do this.

Is what I'm trying to accomplish possible? If so, how do I do this?


Solution

  • Instead of using the annotations to find a key by a partial key, I've created a bean that manages the keys for me

    1. I removed all the @CacheEvict annotations.
    2. Created a new service that'll manage the eviction for all of our caches

      public interface CacheEvictionService {
          /**
           * Adds the provided key to a global list of keys that we'll need later for eviction
           *
           * @param key the cached key for any entry
           */
          void addKeyToList(String key);
      
          /**
           * Find keys that contain the partial key
           *
           * @param partialKey the cached partial key for an entry
           * @return List of matching keys
           */
          List<String> findKeyByPartialKey(String partialKey);
      
          /**
           * Evicts the cache and key for an entry matching the provided key
           *
           * @param key the key of the entry you want to evict
           */
          void evict(String key);
      
      }
      
      @Service
      public class CacheEvictionServiceImpl implements CacheEvictionService {
          LinkedHashSet<String> cachedKeys = new LinkedHashSet<>();
      
          @Override
          public void addKeyToList(String key) {
              this.cachedKeys.add(key);
          }
      
          @Override
          public List<String> findKeyByPartialKey(String partialKey) {
              List<String> foundKeys = new ArrayList<>();
              for (String cachedKey : this.cachedKeys) {
                  if (cachedKey.contains(partialKey)) {
                      foundKeys.add(cachedKey);
                  }
              }
              return foundKeys;
          }
      
          @Override
          @CacheEvict(value = {"valueCodes", "fieldCodes", "qi", "fieldNames", "fieldsByType"}, key = "#key")
          public void evict(String key) {
              this.cachedKeys.remove(key);
          }
      }
      
    3. Instead of using multiple keys, concatenate the different keys into a single string

      @Cacheable(cacheNames = "valueCodes", key = "#value.concat(#fieldId).concat(#projectId)")
      
    4. Send the key to the service every time something is cached

      cacheEvictionService.addKeyToList(StringUtils.join(value, fieldId, projectId));
      
    5. Loop over every existing key that contains the project id (or any other key)

      for (String cachedKey : cacheEvictionService.findKeyByPartialKey(projectId)) {
          cacheEvictionService.evict(cachedKey);
      }