Search code examples
javaspringspring-aopspring-cache

Spring @Cacheable : need to know value return from cache or not


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;
}

Solution

  • 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.