Ashot Golovenko opened SPR-10958 and commented
Currently you can evict only one or all elements in a single cache region. Allowing something like
@CacheEvict(value = "userCache", key = {"key1", "key2"})
would be really handful.
Affects: 3.2.4
12 votes, 14 watchers
Comment From: spring-projects-issues
Phil Webb commented
Usually the key
attribute is a SpEL expression that obtains the key from an input parameter.
e.g.:
@Override
@CacheEvict(value = "default", key = "#p0")
public void evict(Object arg1, Object arg2) {
}
What use-case do you have in mind where two keys would need to be evicted?
Comment From: spring-projects-issues
Ashot Golovenko commented
Well, let's say I have the following logic:
@CacheEvict(value = "userCache", key = "#userId + '/foo1'")
public void updateFoo(long userId, someParams)
@Cacheable(value = "userCache", key = "#userId + '/foo1'")
public void getFoo1(long userId)
@Cacheable(value = "userCache", key = "#userId + '/foo2'")
public void getFoo2(long userId)
The update method changes the database so that calculations of both getFoo1 and getFoo2 methods are obsolete. Now I can evict only one key or all. Ideally I'd really love to have a keymask support so I could evict all keys starting like "#userId*" but array of keys is also fine.
Comment From: spring-projects-issues
Phil Webb commented
You could consider using different cache stores rather than using the key:
@CacheEvict(value = "foo1Cache", key = "#userId")
public void updateFoo(long userId, someParams)
@Cacheable(value = "foo1Cache", key = "#userId")
public void getFoo1(long userId)
@Cacheable(value = "foo2Cache", key = "#userId")
public void getFoo2(long userId)
You can then use:
@CacheEvict(value = {"foo1Cache", "foo2Cache"}, key = "#userId")
Comment From: spring-projects-issues
Lives commented
This would be very helpful in different use cases. For eg : I have a city master which is cached. The service for city master can accept a collection of cities for creation / modification
Since currently cache evict does not support multiple keys , developer ends up with clearing the entire cache. Using SPEL developers can pass the list of keys to be removed from cache.If the cache evict aspect can handle it, it would be very useful.
Comment From: spring-projects-issues
shirish commented
This is a really good feature request. One such use case that I have encountered is to update user messages. Ideally an user can have messages in multiple locales I would like to evict all the messages with key= "{#userid_en, #userid_de}" instead of maintaining a separate cache for each locale.
Also if eviction is possible using regex of the key that would solve this entirely, how ever I am not sure about how big a task this is. some thing like key={"#userid_*"} should evict every thing under that key regex.
Comment From: spring-projects-issues
Stéphane Nicoll commented
My own personal feeling about it is that if you need to do this you probably reached the boundaries of what's safe to do declaratively. If we're talking about multiple keys and spel then it starts to be awkward for several reasons:
- We need this to be consistent so we should support that everywhere we support the key attribute (not only cache evict)
- If you can do that with SpEL you must be able to do that programmatically. Yet, the
KeyGenerator
returns an object so we'll have to deal with the return type and handle a collection of something explicitly (which means you can't use that as a single key anywhere even though I doubt someone was doing that) - Going further down that road, things like pattern matching to retrieve the number of elements comes to mind (there is an example above) and we don't want to do that with SpEL.
Since this adds a lot of complexity I want to be sure that this is really needed so please cast your vote (and add your use case please).
Comment From: spring-projects-issues
Johannes Kuhs commented
For more advanced caching scenarios, some concept of having multiple keys is definitely necessary in my opinion. I'm not sure it needs to be in the form of allowing multiple key
values though. The key
is used for retrieving a cache entry but really we just need a way to evict specific entries. So instead, it might make more sense to add a new evictKeys
parameter. evictKeys
simply point to the key
and can thus be used to evict the actual entry.
Another use-case for this kind of functionality would be the caching of different product information. A product might have skus, images, prices, features, facets, and other information attached to it that require more expensive operations (e.g. looping through all images to find a certain image type + view, getting a specific customer price, creating a distinct set of facets across skus, etc.). It would be nice to be able to cache the results of these kind of operations and simply evict them based on the product ID when the product is updated.
Updating the existing annotations to support this could look like this:
@Cacheable(value = "productImages", key = "#product.id + #imageType", evictKeys = { "#product.id" })
public Image getProductImageOfType(Product product, String imageType)
@CacheEvict(value = "productImages", evictKeys = { "product.id" })
public void updateProduct(Product product)
Comment From: spring-projects-issues
Kyle Lieber commented
We could really use this functionality as well. I think the suggestions made by @jkuhs
would work. Here my scenario if it helps:
We have a Policy
and a Product
.
I have a Policy
which is identified by a policyId
and each Policy
can have multiple Products
which are identified by the policyId
and the productCode
.
So I have a ProductService
with a method for getting a product by policyId
and productCode
.
public class ProductService {
@Cacheable("products")
public Product getProduct(Integer policyId, String productCode) { }
}
Then I have a PolicyService
which has a method for deleting a Policy
when given the policyId
.
public class PolicyService {
public void deletePolicy(Integer policyId) { }
}
The PolicyService#deletePolicy
will also delete all products for that policy but I have no way to clear the products
cache for that policy.
My workaround options are:
- Clear all entries in the cache. This is really not an option because this will affect all products for all policies.
- Do something really ugly like this:
public class ProductService {
@Cacheable("products")
public Product getProduct(Integer policyId, String productCode) { }
public List<Product> getAllProducts(Integer policyId) { }
@CacheEvict("products")
public void evictFromCache(Integer policyId, String productCode) {
// this method doesn't do anything and is only here for evicting the cache
}
}
public class PolicyService {
public void deletePolicy(Integer policyId) {
// first delete the policy
doDelete(policyId);
// then evict the products
for (Product product : productService.getAllProducts(policyId)) {
productService.evictFromCache(policyId, product.code);
}
}
}
Comment From: spring-projects-issues
Johannes Kuhs commented
To add to my previous comment, it would be nice to also have a evictKeysGenerator
parameter on the Cacheable
annotation. It would follow the same concept as keyGenerator
but have a return type of String[]
(or even Object[]
- the point being that multiple evict keys are supported).
I'm really hoping some support for multiple evict keys will be implemented. In it's current form, the cache invalidation is quite limited for more advanced scenarios.
Comment From: spring-projects-issues
Stéphane Nicoll commented
We discussed this internally and decided to reject this feature. This is mostly programming by annotations and it would make the API and implementation much more complex. This idea of a separate KeysGenerator
interface for evictions is an obvious example.
While we could implement a simpler solution for some cases, the solution would become inconsistent overall as all the other annotation attributes and interface method handle a single value for the key.
Comment From: spring-projects-issues
Johannes Kuhs commented
It's disappointing to hear that. Would you mind elaborating on why you think this would make the API and implementation "much more complex"? Not sure I understand the argument for a KeysGenerator
adding that much complexity. The concept for key generators is not a new one after all.
If this decision is mostly based on not wanting to change the current interfaces, would you consider alternative solutions to support multiple evict keys in some capacity? I haven't thought about alternative solutions a lot, but maybe a separate set of annotations for advanced use, or some hooks that would allow for a more programmatic approach after the logic behind the @Cacheable
and @CacheEvict
annotations has been executed?
Comment From: spring-projects-issues
Sarath Akula commented
As @jkuhs
mentioned, can it be a separate set of annotations for advanced use.It would avoid writing custom code for clearing up the cache in our application.
Comment From: spring-projects-issues
Ranadeep Sharma Hidangmayum commented
Hi Team,
The use cases mentioned by everyone here are very common ones, infact. Here is another usecase -
Let's consider an application where insurance plans for participants belonging to various clients are being read/updated in DB. This application has lots of methods where numerous plan objects have been pumped to cache(s), no matter how many.
@Cacheable(value="participantCache" , key = "{#clientId, #planId, #participantId}")
public List<ParticipantPlanDetails> getParticipantPlanDetails(String clientId, String planId, String participantId) { ... }
@Cacheable(value="clientCache" , key = "{#clientId, #planId}")
public PlanInfo getEasePlanInfo(String clientId, String planId) { ... }
Now, I want to delete items from applicable cache(s) where the item's key is matching partially with the collective *clientId * used for deleting all relevant data from DB for a particular client.
@CacheEvict(key = "#clientId")
public void deleteClient(String clientId) { ... }
In short, I would like the above CacheEvict_to be able to convey to cache handler that all the objects for this specific _clientId be cleared, including the List
Comment From: spring-projects-issues
Ranadeep Sharma Hidangmayum commented
Kindly review this feature against my use case (mentioned in comments) for possible enhancement.
Comment From: spring-projects-issues
Stéphane Nicoll commented
As I already explained above, we've made up our mind on this feature and decided not to implement it.
Comment From: spring-projects-issues
Ashok commented
@snicoll
,
If rejecting this request is only because many other annotations expect a single key, is it possible have a single, but the key supporting regex (which can match one or more elements)?
Comment From: spring-projects-issues
Ashok commented
Only the evict annotation's key regex matching more than one element in each of the cacheNames
Comment From: themafole
Hi There,
We're still encountering scenarios where this feature / implementation would have been of great importance.
Comment From: sergiocard
Hi There,
We're still encountering scenarios where this feature / implementation would have been of great importance.
I'me facing too: the following is a classic scenario in wich we handle different languages for same event. So Ho we can evict all the entries for the same eventId ?
@Cacheable(value = "EventsCache")
public Response getEvent(int eventId, String language) {
return service.getEvent(eventId, language);
}
@CacheEvict(value = "EventsCache")
public void evictEvent(int eventId) {}
Comment From: william-aqn
Here's another example: Pagination of entities. (the key will be entityId+currentPage) We need to reset everything with entityId but without currentPage.
How? Need add this construction https://github.com/spring-projects/spring-framework/issues/15586#issuecomment-453405906
Comment From: waitpigfly
Johannes Kuhs commented
For more advanced caching scenarios, some concept of having multiple keys is definitely necessary in my opinion. I'm not sure it needs to be in the form of allowing multiple
key
values though. Thekey
is used for retrieving a cache entry but really we just need a way to evict specific entries. So instead, it might make more sense to add a newevictKeys
parameter.evictKeys
simply point to thekey
and can thus be used to evict the actual entry.Another use-case for this kind of functionality would be the caching of different product information. A product might have skus, images, prices, features, facets, and other information attached to it that require more expensive operations (e.g. looping through all images to find a certain image type + view, getting a specific customer price, creating a distinct set of facets across skus, etc.). It would be nice to be able to cache the results of these kind of operations and simply evict them based on the product ID when the product is updated.
Updating the existing annotations to support this could look like this:
```java @Cacheable(value = "productImages", key = "#product.id + #imageType", evictKeys = { "#product.id" }) public Image getProductImageOfType(Product product, String imageType)
@CacheEvict(value = "productImages", evictKeys = { "product.id" }) public void updateProduct(Product product) ```
use case like this:
// ex: find products of shop via rpc
@Cacheable(value = "products", key = "#shop.id+ '_' + #type")
public List<Product> findProductsOfType(Shop shop, String type)
// support key & keys: key require a object result, keys require a list of object
@CacheEvict(value = "products", keys = "#root.target.getCacheKeys(#p1)")
public void updateProduct(Product product)
// get multi cache keys related to product
public List<Object> getCacheKeys(Product product){
Integer shopId = product.getShopId();
String types = ...;// get types of shop
List<Object> keys = new LinkedList<>();
for(String t: types){
keys.add(String.format("%d_%s", shopId, t));
}
return keys;
}
Comment From: LY1806620741
@spring-projects-issues
Hi,My requirement is to cache paginated data with filtering. I think we can build a Cache by Multi-Level cache.
we have code:
@Cacheable(cacheNames = "cacheName")
public GenericPageResult<NoticeBlockListDO> getByFilterThenPage(
//index params
Integer params1,
String params2,
//filter params
TypeEnum filter1,
//Pagination params
Integer currentPage,
Integer pageSize
) {
return ...;
}
@CacheEvict(cacheNames = "cacheName")
public void removeByIndex(Integer params1, String params2){
}
@CacheEvict(cacheNames = "cacheName")
public void removeByFilter(Integer params1, String params2,TypeEnum filter1){
}
@CacheEvict(cacheNames = "cacheName")
public void removeByFilterAndOnePage(Integer params1, String params2,TypeEnum filter,Integer currentPage,Integer pageSize){
}
we can design then use this? define 3 level namespace,then build 3 level Map<String(indexkey),Map
@Cacheable(cacheNames = "cacheName",levelkey={
"#params1,#params2",
"#filter",
"#currentPage,#pageSize",
})