Spring Cache+Redis cache doesn't calculate keys based on method/class names - spring-boot

I'm using redisson with a jcache abstraction, simply put I have this:
public class MyService{
#Cacheable("cacheA")
public String returnSomethingAfterLongTime(String parameter){
//...
}
#Cacheable("cacheA")
public String returnSomethingElse(String parameter){
}
}
Problem is that both of them create a redis key like "cacheA::parameter", in other words the class and method name are not taken into account.
This causes a problem if the string "parameter" is a common word because I have to be aware of every part of code where "cacheA" is used so to make sure that no inefficiency is brought up due to the fact that the "parameter" key could be replicated among calls.
Is there something that I'm doing wrong?

It looks like you can specify a "key" attribute to customize it to cache based on method name.
Spring Cacheable key attribute
There are a lot of good examples and answers on this post.
I've never personally used Spring Cache, but it looks like you can specify #Cacheable("cacheA", key="#parameter") and the value of parameter will be used as the key rather than the word "parameter".

Related

How to programmatically invalidate a Quarkus Cache?

I´m running into a problem where using the #CacheInvalidate annotation is not enough anymore.
One method has to erase two different caches, one of them uses two of the given arguments and the other uses all three.
#CacheInvalidate(cacheName = "cache-with-two-identifiers")
#CacheInvalidate(cacheName = "cache-with-three-identifiers")
public void doSomething(#CacheKey String identifier, #CacheKey String anotherIdentifier, String aThirdIdentifier){
}
The #CacheKey annotated arguments are used for the cache with two identifiers, so I cannot annotate the third argument as well, but it would be required to match the keys of the cache-with-three-identifiers.
The only solution I see so far is programmatically clearing the third-arg-cache within the method itself. How would you do that in Quarkus?
There is currently no programmatic caching API for Quarkus.
There is already an open issue for it, you can +1 for it and provides feedback: https://github.com/quarkusio/quarkus/issues/8140

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

How can I map URL containing colon to my Spring controller?

I want to map a URL (for example, http://example.com/v1/books:search) containing colons to my Spring MVC controller, but I can't make it work.
#RequestMapping("/v1/books")
public class BooksController {
#GetMapping(":search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
When I test this API, Spring returns 404 NOT_FOUND to me. It seems that Spring doesn't support colons in URL mapping.
Is there any method to make it work? Thanks.
I hit this attempting to do similar so I thought I'd share my findings.
With using most defaults and your code, the search method will be mapped to /v1/books/:search which is obviously not quite what you want. There are two places that I've found so far that get in the way of changing this. The first is the AntPathMatcher's combine method. This method will attempt to put a path separator (/) between segments. The second place is within the RequestMappingInfo's path parsing code. The former can be replaced easily. The latter not so much.
As the methods that tend to be problematic involve combining multiple #RequestMapping annotations, what I've found to work is to simply side-step combinations. On my controller class, I have a #Controller annotation and any defaults for #RequestMapping, but not a path attribute. On each method, the full path is then added. This isn't great, but it does get collection-level special "methods" to function properly. In your example, this would look like:
#Controller
#RequestMapping
public class BooksController {
#GetMapping("/v1/books:search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
Long story short: Do not do this - use / as a separator for the method.
A bit more detail: Have a look at Spring Framework issue #24771 that suggests that the team actually moves away from various ways to handle non-standard URL mappings in favor of simpler logic of URL processing, after entangling in a series of various issues with similar concepts. This "custom method" thing is unlikely to get a first class support in Spring, as a result.
Therefore, despite what Google does, just do this as a normal person and use /v1/books/search path:
#RequestMapping("v1/books")
public class BooksController {
#GetMapping("search")
public Page<Book> search(#RequestParam String author) {
// Return books written by the author.
}
}

Update field annotated with #Value in runtime

Let's imagine we have such a component in Spring:
#Component
public class MyComponent {
#Value("${someProperty}")
private String text;
}
If we define the property placeholder:
<context:property-placeholder location="classpath:myProps.properties"/>
And myPropos.properties contains the value for someProperty the value will be injected to the text field when the context is initialized. That's quite simple and easy.
But let's say that I have a service that enables user to change the value of the someProperty:
public void changeProp(String name, String newValue);
Is there a chance I can re-inject the newValue to text field. I mean it should be quite straight forward.. Basically it's nothing different than the after-initialization injection. I can not imagine that Spring does not have support for this? Can I fire some event or something?
I could do this on my own basically, but I wander is it maybe something there already? If not does anyone know what Spring class is in fact handling the injections at the first place? I could probably reuse the code there do perform this on my own if a solution does not exists.
I expect spring does not have a support for this, because the normal injection is done while creating the bean, but not will it is put in service.
Anyway: in this blog entry "Reloadable Application Properties with Spring 3.1, Java 7 and Google Guava", you can find the idea for an solution.
The key idea is to use a post processor to build a list of all fields with property fields. And if the properties are changed on can use this list to update the fields.

#Cacheable with Spring 3.1

I am using #Cacheable with Spring 3.1. I little bit confused with value and key mapping parameters in Cacheable.
Here is what I am doing:
#Cacheable(value = "message", key = "#zoneMastNo")
public List<Option> getAreaNameOptionList(String local, Long zoneMastNo) {
//..code to fetch data form database..
return list;
}
#Cacheable(value = "message", key = "#areaMastNo")
public List<Option> getLocalityNameOptionList(String local, Long areaMastNo) {
//..code to fetch data form database..
return list;
}
What happening here, second method is dependent on selected value of first method,
but issue is suppose when I pass zoneMastNo = 1 and areaMastNo = 1 then second method returns first methods result.
Actually, I have lots of services hence, I am looking to use common value for cacheable for specific use cases.
Now my questions are:
How can I solve this issue?
Is it good idea that use cacheable for every services?
After specified time will cache completely remove from memory without
using #CacheEvict ?
How can I solve this issue?
I assume zoneMastNo and areaMastNo are completely different keys, by which I mean List<Option> for zoneMastNo = 1 is not the same as List<Option> for areaMastNo = 1. This means you need two caches - one keyed by zone and the other by area. However you are explicitly using only one cache named message. Quoting 29.3.1 #Cacheable annotation:
#Cacheable("books")
public Book findBook(ISBN isbn) {...}
In the snippet above, the method findBook is associated with the cache named books.
So if I understand correctly, you should basically use two different caches:
#Cacheable(value = "byZone", key = "#zoneMastNo")
public List<Option> getAreaNameOptionList(String local, Long zoneMastNo)
//...
#Cacheable(value = "byArea", key = "#areaMastNo")
public List<Option> getLocalityNameOptionList(String local, Long areaMastNo)
Also are you sure these methods won't have a different result depending on local parameter? If not, what is it used for?
Is it good idea that use cacheable for every services?
No, for the following reasons:
some methods are just fast enough
...and caching introduced some overhead on its own
some services call other services, do you need caching on every level of hierarchy
caching needs memory, a lot of it
cache invalidation is hard
After specified time will cache completely remove from memory without using #CacheEvict ?
That totally depends on your cache implementation. But every sane implementation has such an option, e.g. EhCache.
question 3:
it depends on your cache expiration configuration. if you use ehcache, change the settings in ehcache.xml.

Resources