Spring – How update/remove an item already cached within a collection of items

cachingehcachespringspring-3spring-cache

I am working with Spring and EhCache

I have the following method

@Override
@Cacheable(value="products", key="#root.target.PRODUCTS")
public Set<Product> findAll() {
    return new LinkedHashSet<>(this.productRepository.findAll());
}

I have other methods working with @Cacheable and @CachePut and @CacheEvict.

Now, imagine the database returns 100 products and they are cached through key="#root.target.PRODUCTS", then other method would insert – update – deleted an item into the database. Therefore the products cached through the key="#root.target.PRODUCTS" are not the same anymore such as the database.

I mean, check the two following two methods, they are able to update/delete an item, and that same item is cached in the other key="#root.target.PRODUCTS"

@Override
@CachePut(value="products", key="#product.id")
public Product update(Product product) {
    return this.productRepository.save(product);
}

@Override
@CacheEvict(value="products", key="#id")
public void delete(Integer id) {
    this.productRepository.delete(id);
}

I want to know if is possible update/delete the item located in the cache through the key="#root.target.PRODUCTS", it would be 100 with the Product updated or 499 if the Product was deleted.

My point is, I want avoid the following:

@Override
@CachePut(value="products", key="#product.id")
@CacheEvict(value="products", key="#root.target.PRODUCTS")
public Product update(Product product) {
    return this.productRepository.save(product);
}

@Override
@Caching(evict={
        @CacheEvict(value="products", key="#id"),
        @CacheEvict(value="products", key="#root.target.PRODUCTS")
})
public void delete(Integer id) {
    this.productRepository.delete(id);
}

I don't want call again the 500 or 499 products to be cached into the key="#root.target.PRODUCTS"

Is possible do this? How?

Thanks in advance.

Best Solution

Caching the collection using the caching abstraction is a duplicate of what the underlying caching system is doing. And because this is a duplicate, it turns out that you have to resort to some kind of duplications in your own code in one way or the other (the duplicate key for the set is the obvious representation of that). And because there is duplication, you have to sync state somehow

If you really need to access to the whole set and individual elements, then you should probably use a shortcut for the easiest leg. First, you should make sure your cache contains all elements which is not something that is obvious. Far from it actually. Considering you have that:

//EhCacheCache cache = (EhCacheCache) cacheManager.getCache("products");


@Override
public Set<Product> findAll() {
    Ehcache nativeCache = cache.getNativeCache();
    Map<Object, Element> elements = nativeCache.getAll(nativeCache.getKeys());
    Set<Product> result = new HashSet<Product>();
    for (Element element : elements.values()) {
        result.add((Product) element.getObjectValue());
    }
    return Collections.unmodifiableSet(result);
}

The elements result is actually a lazy loaded map so a call to values() may throw an exception. You may want to loop over the keys or something.

You have to remember that the caching abstraction eases the access to the underlying caching infrastructure and in no way it replaces it: if you had to use the API directly, this is probably what you would have to do in some sort.

Now, we can keep the conversion on SPR-12036 if you believe we can improve the caching abstraction in that area. Thanks!

Related Question