Reputation: 77
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
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
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