I am using redis cache in spring boot application. Requirement is - We need data list on certain date filter. So to achieve that I used cacheable annotation.
Now when I run it for first time, it fetches records from db. When run multiple times it returns data stored in cache. Perfect! till here it is running fine.
Then I make an entry in db using my jpa repository, with CachePut annotation.
Now, assuming that I will again get the cache data with updated entry, I make a request to my GET api which I tried above. But to my surprise it fails to execute. Forget about the latest entry, this time it didn't even show earlier cached data list also.
@Cacheable(cacheNames = "custDetails", key="#newApplicationDate")
@Override
public List<FetchResponse> fetchAll2(String newApplicationDate) {
//some code to return list
}
@CachePut(cacheNames = "custDetails", key="#strDate")
@Override
public CustomerDetail updateCustomer(CustomerDetail customerDetail,String strDate) {
CustomerDetail customerDetail2 = custRepository.saveAndFlush(customerDetail);
return customerDetail2;
}
I crossed check using print statements, both newApplicationDate and strDate date values are same.
In redis client I checked keys * it showed something like:
1) "custDetails:\xac\xed\x00\x05t\x00\n2023-11-01"
and in spring boot console I am getting following error:
java.lang.ClassCastException: com.myapp.model.CustomerDetail cannot be cast to java.util.List
at com.myapp.service.MyServiceImpl$$EnhancerBySpringCGLIB$$347be321.fetchAll2(<generated>) ~[classes/:na]
How can I update the existing cached list using cachePut annotation?
=========================================
I also tried below code i.e. calling a same return type method again, this time I put the cachePut annotation now in this new method:
@Override
public CustomerDetail updateCustomer(CustomerDetail customerDetail,String strDate) {
// TODO Auto-generated method stub
System.out.println("!!!!! "+strDate+" !!!!!");
CustomerDetail customerDetail2 = custRepository.saveAndFlush(customerDetail);
if(customerDetail2!=null) {
refreshCache(strDate);
}
return customerDetail2;
}
@CachePut(cacheNames = "custDetails", key="#strDate")
public List<FetchResponse> refreshCache(String strDate){
return ekycService.fetchAll2(strDate);
}
But no success..
=================UPDATE================
Now I have put both Cacheable and CachePut in same service class.
@Cacheable(value = "custDetails", key = "#date")
@Override
public List<CustomerDetail> fetchByDate(String date) {
return custRepository.findByCreateDatess(date);
}
@CachePut(value = "custDetails", key="#strDate")
@Override
public CustomerDetail updateCustomer(CustomerDetail customerDetail,String strDate) {
CustomerDetail customerDetail2 = custRepository.saveAndFlush(customerDetail);
return customerDetail2;
}
Again same issue. When I try adding CustomerDetail to the List<CustomerDetail> , then onwards I am not getting List. I am getting below error instead when trying to get List<CustomerDetail>:
java.lang.ClassCastException: com.myapp.model.CustomerDetail cannot be cast to java.util.List
If caching can not sync with its own existing list, then why on earth it is so talked about concept!
As @WildDev explained in his comment above, the return types of the value to cache must match for a given key when using a specifically "named" cache.
In your examples, this would equate to:
Remember, your "customerDetails" cache will have the following entry after the
fetchByDate(..)service method is called:This means you need logic in your
updateCustomer(..)service method to update theListofCustomerDetailobjects returned by the update method to subsequently update the cache entry for the "date" key.Arguably, if any
CustomerDetailentry in theListis updated then the cache entry should be invalidated. Therefore, yourupdateCustomer(..)method would rather be:In this case, the service method return types don't need to match.
Yes, this will remove the cached
Listentirely, but the next time the particular date is requested on fetch (i.e.fetchByDate(..)), then theListcan be lazily reloaded/cached.This approach might come at the expense of more latency, but consume less memory.
Also, you need to consider how frequently individual
CustomerDetailobjects stored in the cacheListare updated, in addition to the "concurrency" of the updates. You may possibly run into race conditions with your current arrangement, without some form of synchronization (for example), especially, if yourListofCustomerDetailobjects are large in size and the concurrency and frequency of updates are high. This in itself defeats the purpose of caching.Other things to consider is if some other application is updating the backend data source outside of this application using caching. There are many things to think about, technically.
Personally, I'd prefer to cache individual objects and not collections of those objects. However, if you are caching a collection, then I'd argue to 1) keep the
Listof things related and 2) small.There are several ways to handle what you want to do in your case, but I present 1 way here.
See the example test I wrote for this post.