Want to refresh cache in Redis using Spring boot Considering multiple pods - spring

Currently I am using annotation #CacheEvict using cron job, after that reloading again using new calls, but not happy with this approach as I have multiple nods.
My requirement is
Reload cache with new data after a certain time limit.
Should be working in multiple nodes.
Looking for a elegant design.

Did you try using cache manager?
https://www.baeldung.com/spring-multiple-cache-managers
Spring #CacheEvict annotation is used to evict cache.
The #CacheEvict is used at method level.
The #Cacheable annotation sets the value in cache and on the contrary #CacheEvict evicts the cache.
At one method we can use #Cacheable to cache result and at another method we can use #CacheEvict to evict cache.
The #CacheEvict annotation is introduced in Spring 3.1.
The #CacheEvict has following attributes.
String[] cacheNames
Cache names to evict.
String[] value
Alias for cacheNames.
String key
SpEL expression for computing the key dynamically.
String keyGenerator
The bean name of the custom KeyGenerator to use.
String cacheManager
The bean name of the custom CacheManager. It is used to create default CacheResolver if none is set already.
String cacheResolver
The bean name of the custom CacheResolver to use.
String condition
SpEL expression used for making the cache eviction operation conditional.
boolean allEntries
If true, all the entries inside the cache are removed.
boolean beforeInvocation
If true, the cache eviction will occur before the method is invoked.

I think you take the risk to never be satisfied by a solution crafted inside your application code to solve an architectural design problematic of the deployment of the application.
If you need to control the behavior of a variable set of nodes, you better have to design a small independent tool (e.g. in the form of a micro-service) that will receive "meta" requests and do the right thing: calling #CacheEvict and then #Cache entry points of all nodes in the right order. The list of nodes and the list of entry points to be called could be easily defined in parameter files or in a datasource.
You can also make your pods listening to a stream of events or an AMQP broadcaster.

Related

Is it possible to #CacheEvict keys that match a pattern?

Is there something along the lines of #CacheEvict(value = "FOO", key = "baz*") so that when the cache FOO contains keys baz_1 and baz_2 they get evicted?
Assuming that you have spring-boot-starter-cache as dependency, spring boot auto-configures a CacheManager bean named cacheManager.
Also, assuming you have spring-boot-starter-data-redis as a dependency, RedisCacheManager is picked as the CacheManager implementation.
#CacheEvict (and the caching abstraction API) doesn't let you the option to evict by prefix, but using an AOP advice (or elsewhere where fits), you can take advantage of the underlying implementation:
RedisCache redisCache = (RedisCache) cacheManager.getCache("FOO");
redisCache.getNativeCache().clean("FOO", "baz*".getBytes());
Didn't try it actually, but I think this should work.
Likewise, you can adapt to other caching implementation.
The shortcoming of this approach, is that you'll have to change your code upon changing the cache implementation.

Spring Caching not working for findAll method

I have recently started working on caching the result from a method. I am using #Cacheable and #CachePut to implement the desired the functionality.
But somehow, the save operation is not updating the cache for findAll method. Below is the code snippet for the same:
#RestController
#RequestMapping(path = "/test/v1")
#CacheConfig(cacheNames = "persons")
public class CacheDemoController {
#Autowired
private PersonRepository personRepository;
#Cacheable
#RequestMapping(method = RequestMethod.GET, path="/persons/{id}")
public Person getPerson(#PathVariable(name = "id") long id) {
return this.personRepository.findById(id);
}
#Cacheable
#RequestMapping(method = RequestMethod.GET, path="/persons")
public List<Person> findAll() {
return this.personRepository.findAll();
}
#CachePut
#RequestMapping(method = RequestMethod.POST, path="/save")
public Person savePerson(#RequestBody Person person) {
return this.personRepository.save(person);
}
}
For the very first call to the findAll method, it is storing the the result in the "persons" cache and for all the subsequent calls it is returning the same result even if the save() operation has been performed in between.
I am pretty new to caching so any advice on this would be of great help.
Thanks!
So, a few things come to mind regarding your UC and looking at your code above.
First, I am not a fan of users enabling caching in either the UI or Data tier of the application, though it makes more sense in the Data tier (e.g. DAOs or Repos). Caching, like Transaction Management, Security, etc, is a service-level concern and therefore belongs in the Service tier IMO, where your application consists of: [Web|Mobile|CLI]+ UI -> Service -> DAO (a.k.a. Repo). The advantage of enabling Caching in the Service tier is that is is more reusable across your application/system architecture. Think, servicing Mobile app clients in addition to Web, for instance. Your Controllers for you Web tier may not necessarily be the same as those handling Mobile app clients.
I encourage you to read the chapter in the core Spring Framework's Reference Documentation on Spring's Cache Abstraction. FYI, Spring's Cache Abstraction, like TX management, is deeply rooted in Spring's AOP support. However, for your purposes here, let's break your Spring Web MVC Controller (i.e. CacheDemoController) down a bit as to what is happening.
So, you have a findAll() method that you are caching the results for.
WARNING: Also, I don't generally recommend that you cache the results of a Repository.findAll() call, especially in production! While this might work just fine locally given a limited data set, the CrudRepository.findAll() method returns all results in the data structure in the backing data store (e.g. the Person Table in an RDBMS) for that particular object/data type (e.g. Person) by default, unless you are employing paging or some LIMIT on the result set returned. When it comes to caching, always think a high degree of reuse on relatively infrequent data changes; these are good candidates for caching.
Given your Controller's findAll() method has NO method parameters, Spring is going to determine a "default" key to use to cache the findAll() method's return value (i.e. List<Person).
TIP: see Spring's docs on "Default Key Generation" for more details.
NOTE: In Spring, as with caching in general, Key/Value stores (like java.util.Map) are the primary implementation's for Spring's notion of a Cache. However, not all "caching providers" are equal (e.g. Redis vs. a java.util.concurrent.ConcurrentHashMap, for instance).
After calling the findAll() Controller method, your cache will have...
KEY | VALUE
------------------------
abc123 | List of People
NOTE: the cache will not store each Person in the list individually as a separate cache entry. That is not how method-level caching works in Spring's Cache Abstraction, at least not by default. However, it is possible.
Then, suppose your Controller's cacheable getPerson(id:long) method is called next. Well, this method includes a parameter, the Person's ID. The argument to this parameter will be used as the key in Spring's Cache Abstraction when the Controller getPerson(..) method is called and Spring attempts to find the (possibly existing) value in the cache. For example, say the method is called with controller.getPerson(1). Except a cache entry with key 1 does not exist in the cache, even if that Person (1) is in list mapped to key abc123. Thus, Spring is not going to find Person 1 in the list and return it, and so, this op results in a cache miss. When the method returns the value (the Person with ID 1) will be cached. But, the cache now looks like this...
KEY | VALUE
------------------------
abc123 | List of People
1 | Person(1)
Finally, a user invokes the Controller's savePerson(:Person) method. Again, the savePerson(:Person) Controller method's parameter value is used as the key (i.e. a "Person" object). Let's say the method is called as so, controller.savePerson(person(1)). Well, the CachePut happens when the method returns, so the existing cache entry for Person 1 is not updated since the "key" is different, so a new cache entry is created, and your cache again looks like this...
KEY | VALUE
---------------------------
abc123 | List of People
1 | Person(1)
Person(1) | Person(1)
None of which is probably what you wanted nor intended to happen.
So, how do you fix this. Well, as I mentioned in the WARNING above, you probably should not be caching an entire collection of values returned from an op. And, even if you do, you need to extend Spring's Caching infrastructure OOTB to handle Collection return types, to break the elements of the Collection up into individual cache entries based on some key. This is intimately more involved.
You can, however, add better coordination between the getPerson(id:long) and savePerson(:Person) Controller methods, however. Basically, you need to be a bit more specific about your key to the savePerson(:Person) method. Fortunately, Spring allows you to "specify" the key, by either providing s custom KeyGenerator implementation or simply by using SpEL. Again, see the docs for more details.
So your example could be modified like so...
#CachePut(key = "#result.id"
#RequestMapping(method = RequestMethod.POST, path="/save")
public Person savePerson(#RequestBody Person person) {
return this.personRepository.save(person);
}
Notice the #CachePut annotation with the key attribute containing the SpEL expression. In this case, I indicated that the cache "key" for this Controller savePerson(:Person) method should be the return value's (i.e. the "#result") or Person object's ID, thereby matching the Controller getPerson(id:long) method's key, which will then update the single cache entry for the Person keyed on the Person's ID...
KEY | VALUE
---------------------------
abc123 | List of People
1 | Person(1)
Still, this won't handle the findAll() method, but it works for getPerson(id) and savePerson(:Person). Again, see my answers to the posting(s) on Collection values as return types in Spring's Caching infrastructure and how to handle them properly. But, be careful! Caching an entire Collection of values as individual cache entries could reck havoc on your application's memory footprint, resulting in OOME. You definitely need to "tune" the underlying caching provider in this case (eviction, expiration, compression, etc) before putting a large deal of entires in the cache, particular at the UI tier where literally thousands of requests maybe happening simultaneously, then "concurrency" becomes a factor too! See Spring's docs on sync capabilities.
Anyway, hope this helps aid your understanding of caching, with Spring in particular, as well as caching in general.
Cheers,
-John

What strategies exist for using Spring Cache on methods that take an array or collection parameter?

I want to use Spring's Cache abstraction to annotate methods as #Cacheable. However, some methods are designed to take an array or collection of parameters and return a collection. For example, consider this method to find entites:
public Collection<Entity> getEntities(Collection<Long> ids)
Semantically, I need to cache Entity objects individually (keyed by id), not based on the collection of IDs as a whole. Similar to what this question is asking about.
Simple Spring Memcached supports what I want, via its ReadThroughMultiCache, but I want to use Spring's abstraction in order to support easy changing of the cache store implementation (Guava, Coherence, Hazelcast, etc), not just memcached.
What strategies exist for caching this kind of method using Spring Cache?
Spring's Cache Abstraction does not support this behavior out-of-the-box. However, it does not mean it is not possible; it's just a bit more work to support the desired behavior.
I wrote a small example demonstrating how a developer might accomplish this. The example uses Spring's ConcurrentMapCacheManager to demonstrate the customizations. This example will need to be adapted to your desired caching provider (e.g. Hazelcast, Coherence, etc).
In short, you need to override the CacheManager implementation's method for "decorating" the Cache. This varies from implementation to implementation. In the ConcurrentMapCacheManager, the method is createConcurrentMapCache(name:String). In Spring Data GemFire, you would override the getCache(name:String) method to decorate the Cache returned. For Guava, it would be the createGuavaCache(name:String) in the GuavaCacheManager, and so on.
Then your custom, decorated Cache implementation (perhaps/ideally, delegating to the actual Cache impl, from this) would handle caching Collections of keys and corresponding values.
There are few limitations of this approach:
A cache miss is all or nothing; i.e. partial keys cached will be considered a miss if any single key is missing. Spring (OOTB) does not let you simultaneously return cache values and call the method for the diff. That would require some very extensive modifications to the Cache Abstraction that I would not recommend.
My implementation is just an example so I chose not to implement the Cache.putIfAbsent(key, value) operation (here).
While my implementation works, it could be made more robust.
Anyway, I hope it provides some insight in how to handle this situation properly.
The test class is self-contained (uses Spring JavaConfig) and can run without any extra dependencies (beyond Spring, JUnit and the JRE).
Cheers!
Worked for me. Here's a link to my answer.
https://stackoverflow.com/a/60992530/2891027
TL:DR
#Cacheable(cacheNames = "test", key = "#p0")
public List<String> getTestFunction(List<String> someIds) {
My example is with String but it also works with Integer and Long, and probably others.

Spring MVC #Cacheable annotation on Generic Method

I am using spring MVC with Hibernate
Generic Method
// getAllById
#SuppressWarnings("unchecked")
public <T> List<T> getAllById(Class<T> entityClass, long id)
throws DataAccessException {
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(entityClass)
.add(Restrictions.eq("id", id));
return criteria.list();
}
In controller
List<GenCurrencyModel> currencyList=pt.getAllById(GenCurrencyModel.class,1);
Question
How we can use #Cacheable("abc") annotation in Generic method and destroy the cache on demand using spring mvc + hibernate with generic DAO
According to the example in spring doc it specify annotation on simple method !
#Cacheable("books")
public Book findBook(ISBN isbn) {...}
I actually required, when Id pass to generic method ,it should first look up in cache, and I should also destroy cache on demand !
First of all think about the implications of using Generics for a moment:
You don't know which types you will use in the future. You don't know the cache names either for that matter.
You (may) have no type information, so there is no chance of choosing a specific cache.
The last point can be solved by always providing type information, like entityClass in your method.
Solution 1: One cache
Use one cache and generate a key based on the type.
#Cacheable(value="myCache", key="#entityClass.name + #id")
Solution 2: Use #Caching
While you can use expressions for the key you can't use them for the cache names. #Caching allows you to use multiple #Cachable annotations, each with another cache name.
#Caching (
#Cacheable(value="books", key="#id", condition="#entityClass.name == 'Book'"),
#Cacheable(value="students", key="#id", condition="#entityClass.name == 'Student')
)
Solution 3: Write your own cache provider
This is not much of an effort to do. The Spring default cache provider is just a map after all. Your implementation could use different 'subcaches' for each type.
Clearing the cache is more difficult. The solutions 1 and 3 have only one cache. You cannot clear only 'books' but not 'students'. Solution 2 has that option but you have to provide all possible caches and types.
You could use solution 3 and talk to the cache directly instead of using #CacheEvict.

EhCache: #CacheEvict on Multiple Objects Using Annotations

I understand that using Spring's (3.1) built in CacheManager using the EhCache implementation, there are certain limitations when in proxy mode (the default) as per this post:
Spring 3.1 #Cacheable - method still executed
Consider the scenario I have:
#CacheEvict(value = "tacos", key = "#tacoId", beforeInvocation = true)
removeTaco(String tacoId) {
// Code to remove taco
}
removeTacos(Set<String> tacoIds) {
for (String tacoId : tacoIds) {
removeTaco(tacoId);
}
}
In this repository method, calling removeTacos(tacoIds) will not actually Evict anything from the Cache because of the limitation described above. My workaround, is that on a service layer above, if I wanted to delete multiple tacos, I'd be looping through each taco Id and passing it into removeTaco(), and never using removeTacos()
However, I'm wondering if there's another way to accomplish this.
1) Is there an SpEL expression that I could pass into the key that would tell EhCache to expire every id in the Set?
e.g. #CacheEvict(value = "tacos", key = "#ids.?[*]") // I know this isn't valid, just can't find the expression.
Or is there a way I can have removeTacos() call removeTaco and actually expire the Cached objects?
The #Caching annotation can be used to combine multiple annotations of the same type such as #CacheEvict or #CachePut, this is the example from the Spring documentation
#Caching(evict = { #CacheEvict("primary"), #CacheEvict(value="secondary", key="#p0") })
public Book importBooks(String deposit, Date date)
You can do one of two things
#CacheEvict(value = "tacos", allEntries = true)
removeTacos(Set<String> tacoIds)
which is not so bad if tacos are read a lot more than they are removed
OR
removeTacos(Set<String> tacoIds) {
for (String tacoId : tacoIds) {
getTacoService().removeTaco(tacoId);
}
}
by calling the service (proxy) you invoke the cache eviction.
AFAIK #CacheEvict supports only removing single entry (by key) or all entries in given cache, there's no way to remove at once multiple entries. If you want to put, update or remove multiple objects from cache (using annotations) and you may switch to memcached take a look at my project Simple Spring Memcached (SSM).
Self invocations don't go through the proxy so one of the solution is to switch to other mode than proxy. Anther solution (I'm not recommending it) may be keeping reference to the service in service (as an autowired field) and use it to invoke removeTaco.
Several months ago I had similar issue in one of my projects. It didn't use Spring Cache but SSM which also requires proxy. To made it work I moved caching (annotations) from service to DAO (repositories) layer. It solved problem with self invocation.

Resources