问题
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.
回答1:
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!
回答2:
I think something like this schould work... Actually it's only a variation if "Stéphane Nicoll" answer ofcourse, but it may be useful for someone. I write it right here and haven't check it in IDE, but something similar works in my Project.
Override CacheResolver:
@Cacheable(value="products", key="#root.target.PRODUCTS", cacheResolver = "customCacheResolver")
Implement your own cache resolver, which search "inside" you cached items and do the work in there
public class CustomCacheResolver implements CacheResolver{ private static final String CACHE_NAME = "products"; @Autowired(required = true) private CacheManager cacheManager; @SuppressWarnings("unchecked") @Override public Collection<? extends Cache> resolveCaches(CacheOperationInvocationContext<?> cacheOperationInvocationContext) { // 1. Take key from query and create new simple key SimpleKey newKey; if (cacheOperationInvocationContext.getArgs().length != null) { //optional newKey = new SimpleKey(args); //It's the key of cached object, which your "@Cachable" search for } else { //Schould never be... DEFAULT work with cache if something wrong with arguments return new ArrayList<>(Arrays.asList(cacheManager.getCache(CACHE_NAME))); } // 2. Take cache EhCacheCache ehCache = (EhCacheCache)cacheManager.getCache(CACHE_NAME); //this one we bringing back Ehcache cache = (Ehcache)ehCache.getNativeCache(); //and with this we working // 3. Modify existing Cache if we have it if (cache.getKeys().contains(newKey) && YouWantToModifyIt) { Element element = cache.get(key); if (element != null && !((List<Products>)element.getObjectValue()).isEmpty()) { List<Products> productsList = (List<Products>)element.getObjectValue(); // ---**--- Modify your "productsList" here as you want. You may now Change single element in this list. ehCache.put(key, anfragenList); //this method NOT adds cache, but OVERWRITE existing // 4. Maybe "Create" new cache with this key if we don't have it } else { ehCache.put(newKey, YOUR_ELEMENTS); } return new ArrayList<>(Arrays.asList(ehCache)); //Bring all back - our "new" or "modified" cache is in there now... }
Read more about CRUD of EhCache: EhCache code samples
Hope it helps. And sorry for my English:(
回答3:
Though I don't see any easy way, but you can override Ehcache cache functionality by supplying cache decorator. Most probably you'd want to use EhcahceDecoratorAdapter, to enhance functions used by EhCacheCache put and evict methods.
来源:https://stackoverflow.com/questions/24940976/how-update-remove-an-item-already-cached-within-a-collection-of-items