sumit pramanik
sumit pramanik

Reputation: 77

How to access cache values in Spring

Scenario: I need to access the values of a cache created as part of one method in another method. How do I do it?

public class Invoice {
  private String invoiced;
  private BigDecimal amount;
  //Getters and Setters
}

Method 1: Gets invoked when a customer wants to get list of invoices from UI

@Cacheable(value="invoices")
public List<Invoice> getAllInvoices(String customerId){
...
//Get all invoices from Database and return 
...
return invoices
}

Method 2: Gets invoked when a customer clicks on download on UI

public File downloadInvoice(String invoiceId) {
 //TODO: 
 //Validate if invoiceId is present in the cache. This is a validation step
 //How can I access the cache "invoices" here.
 ...
 //if InvoiceId is present in cache then download from db else throw Exception
 return file;
}

Note: I am not using any caching libraries

Upvotes: 4

Views: 33809

Answers (2)

John Blum
John Blum

Reputation: 7981

As the documentation (and here) on Spring's Cache Abstraction explains, you must enable caching (i.e. using the @EnableCaching annotation with annotation config, or using the <cache:annotation-driven> element with XML config) and declare a bean of type CacheManager to plugin the caching provider of your choice (e.g. Redis).

Of course, when you are using Spring Boot, both of these concerns are "auto-configured" for you, providing you have declared an appropriate caching provider (i.e. a caching provider implementing Spring's Cache Abstraction) on your Spring Boot application's classpath.

For complete list of caching providers supported (i.e. "auto-configured") by Spring Boot, see here.

This (here) is Spring Boot's caching auto-configuration for Redis, when Redis is on the application's classpath. Notice the declaration of the CacheManager bean, which must be precisely named "cacheManager". This setup is the same for all caching providers.

Now that you know you a "cacheManager" bean of type CacheManager exists in the Spring ApplicationContext, you can use the CacheManager to access the individual caches, which are represented by the Cache (Adapter) interface.

You could then...

@Service
class InvoiceService {

  private CacheManager cacheManager;

  public InvoiceService(CacheManager cacheManager) {
    this.cacheManager = cacheManager;
  }


  public File downloadInvoice(String invoiceId) {

    // Note: name of cache here (i.e. "invoices") must match the name
    // in the `@Cacheable` annotation on getAllInvoices(..).
    Cache invoices = this.cacheManager.getCache("invoices");

    // Now use the "invoices" `Cache` to find a reference to a `Invoice` with "invoiceId".
    // Read/load from database if `Invoice` is "cached"...
    // Do what must be done, etc ...

  }
}

However, you have problem.

Your @Cacheable, getAllInvoices(..) method caches (i.e. maps) "customerId" (key) to a List<Invoice> object (value) in the "invoices" cache.

You might be thinking the returned List of Invoices from your @Cacheable, getAllInvoices(..) service method are cached individually, by "invoiceId". However, I assure you, that is NOT the case!

It is actually...

customerId -> List<Invoice>

That is, a List of Invoice objects mapped by the "customerId" in the "invoices" cache.

There are ways to alter the default behavior (e.g. to cache individual Invoice objects from the List by "invoiceId" in the "invoices" cache if desired, which I have explained in other SO posts on caching Collections), but that is not how your application logic is currently setup and will function!

Therefore, you will need to translate the "invoiceId" into a "customerId" to access the List of Invoice objects from the "invoices" Cache by "customerId", and then iterate the List to find the (possible) cached Invoice by "invoiceId".

Or, you can change your caching logic around (recommended).

Finally...

No caching provider is the same under-the-hood. There may be a way to access individual caches, independently, depending on provider However, in general, you should keep in mind that Spring's Cache representation for the individual caches identified in Spring's caching annotations (e.g. @Cacheable) or the equivalent JSR-107, JCache API annotation equivalents, are not actual "beans" in the Spring Container (unlike the CacheManager).

In Redis, caches are Redis HASHES (I believe).

In GemFire/Geode (which I am most familiar with), caches (i.e. Cache) is a GemFire/Geode Region, which does happen to be a Spring bean in the ApplicationContext.

Also, some caching providers also wrap the underlying data structure (e.g. GemFire/Geode Region) backing the Cache with an appropriate template (e.g. GemfireTemplate, which is by Region).

I am not certain if (Sprig Data) Redis creates a RedisTemplate for each HASH backing a Cache. However, this is the case with GemFire/Geode. So you could do something like this as well...

@Service
class InvoiceService {

  @Resource(name = "invoices")
  private Region<String, Invoice> invoices;

  // ...

}

Or, alternatively (recommended)...

@Service
class InvoiceService {

  @Autowired
  @Qualifier("invoices")
  GemfireTemplate invoicesTemplate;

  // ... 

}

Again, this varies by caching provider and is very provider specific.

The Cache Adapter interface is a generic way to reference the backing caching implementation, and useful if you expect to switch caching providers between environments, or for other reasons.

Again, to access the individual caches (e.g. "invoices"), you inject the CacheManager since not all caching providers create beans for the individual Cache instances.

Remember, you are going to have to change your caching design a bit, I think.

Hope this helps.

Upvotes: 5

Charlie
Charlie

Reputation: 3374

When you annotate a method as @Cacheable, then the first call to that method will store the result of that method in cache. All subsequent calls to this method would return the cached result unless the cache is evicted.

Normal invocation of the getAllInvoices will return the cached results if the results has been cached previously.

For further reading I would suggest this excellent article

Upvotes: 1

Related Questions