Our applications using Spring Cache and need to know if response was returned from cache OR it was actually calculated. We are looking to add a flag in result HashMap that will indicate it. However whatever is returned by method, it is cached so not sure if we can do it in calculate method implementation. Is there any way to know if calculate method was executed OR return value coming from cache when calling calculate method?
Code we are using for calculate method -
@Cacheable(
cacheNames = "request",
key = "#cacheMapKey",
unless = "#result['ErrorMessage'] != null")
public Map<String, Object> calculate(Map<String, Object> cacheMapKey, Map<String, Object> message) {
//method implementation
return result;
}
With a little extra work, it is rather simple to add a bit of state to your @Cacheable
component service methods.
I use this technique when I am answering SO questions like this to show that the value came from the cache vs. the service method by actually computing the value. For example.
You will notice this @Cacheable
, @Service
class extends an abstract base class (CacheableService
) to help manage the "cacheable" state. That way, multiple @Cacheable
, @Service
classes can utilize this functionality if need be.
The CacheableService
class contains methods to query the state of the cache operation, like isCacheMiss()
and isCacheHit()
. Inside the @Cacheable
methods, when invoked due to a "cache miss", is where you would set this bit, by calling setCacheMiss()
. Again, the setCacheMiss()
method is called like so, inside your @Cacheable
service method.
However, a few words of caution!
First, while the abstract CacheableService
class manages the state of the cacheMiss
bit with a Thread-safe class (i.e. AtomicBoolean
), the CacheableService
class itself is not Thread-safe when used in a highly concurrent environment when you have multiple @Cacheable
service methods setting the cacheMiss
bit.
That is, if you have a component class with multiple @Cacheable
service methods all setting the cacheMiss
bit using setCacheMiss()
in a multi-Threaded environment (which is especially true
in a Web application) then it is possible to read stale state of cacheMiss
when querying the bit. Meaning, the cacheMiss
bit could be true
or false
depending on the state of the cache, the operation called and the interleaving of Threads. Therefore, more work is needed in this case, so be careful if you are relying on the state of the cacheMiss
bit for critical decisions.
Second, this approach, using an abstract CacheableService
class, does not work for Spring Data (CRUD) Repositories based on an interface. As others have mentioned in the comments, you could encapsulate this caching logic in an AOP Advice and intercept the appropriate calls, in this case. Personally, I prefer that caching, security, transactions, etc, all be managed in the Service layer of the application rather than the Data Access layer.
Finally, there are undoubtedly other limitations you might run into, as the example code I have provided above was never meant for production, only demonstration purposes. I leave it to you as an exercise to figure out how to mold these bits for your needs.