Redis: Set different time to live for methods annotated with #Cacheable - java

I have a set of cached methods that look somewhat like this:
#Cacheable(value = "myCacheName", keyGenerator = "myKeyGenerator")
public Product getProduct(ProductRequest request) {
// ...
}
And I need to set different time to live (expiration interval) for objects returned by these methods.
Problem: According to the documentation, the offered way is using #RedisHash(timeToLive=…​) or #TimeToLive annotations on the return type of the methods. However, I don't want to pollute my domain classes with caching related logic. In addition, some of my methods return strings or objects of classes which I can not modify. I would prefer to implement it in a more configurable way. There is also a configuration property called spring.cache.redis.time-to-live, but it applies the same time-to-live in all places.
Question: Is there a way to specify time to live/expiration interval on the method level? Or generally, how to implement it in a more elegant way?

Hi if you want to use only Spring annotations one way to do this is the following.
#CacheConfig annotation allows you to define specific CacheManager to use further more the #Cacheable annotation also allows defining cacheManager
#CacheConfig(cacheNames="myCacheName",cacheManager="timeoutCacheManager")
class ProductReader {
#Cacheable(value = "myCacheName", keyGenerator = "myKeyGenerator")
public Product getProduct(ProductRequest request) {
// ...
}
}
#Bean
public CacheManager timeoutCacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
cacheManager.setDefaultExpiration(mytimeToLive);
return cacheManager;
}
Here is also a fragment of a more extensive cache configuration that is resulting again in a CacheManager. This time it configures multiple regions:
#Bean (name="cacheManager")
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration conf_ready_info = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMillis(50000));
RedisCacheConfiguration conf_base_info = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMillis(60000));
Map<String, RedisCacheConfiguration> cacheConfigurations = new HashMap<String, RedisCacheConfiguration>();
cacheConfigurations.put("base_info", conf_base_info);
cacheConfigurations.put("ready_info", conf_ready_info);
return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory)
.withInitialCacheConfigurations(cacheConfigurations).build();
}
I took the last example from:
set expire key at specific time when using Spring caching with Redis
Using only #Cacheable(value = "myCacheName", keyGenerator = "timeoutCacheManager")

Related

Is cacheName needed at CaffeineCacheManager() constructor?

I have a MultipleCacheManager class that looks like this:
#EnableCaching
public class MultipleCacheManagerConfig {
#Bean
#Primary
public CacheManager mainCacheManager() {
// instantiate caffeine manager and add in specifications
CaffeineCacheManager cacheManager = new CaffeineCacheManager("example1", "example2");
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(4, TimeUnit.HOURS)
.recordStats());
return cacheManager;
}
// these are examples of alternate cache managers if another cache needs to be configured differently.
public CacheManager alternateCaffeineManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager("example3");
cacheManager.setCaffeine(Caffeine.newBuilder()
.expireAfterWrite(1, TimeUnit.HOURS)
.recordStats());
return cacheManager;
}
Do I need to actually pass in my cacheNames in the CaffeieneCacheManager() constructor? As long as #Cacheable has the property #Cacheable(cacheName = "example1", cacheManager = "mainCacheManager") does it matter? The cache manager is technically already connected to the name at that point.
You can always find usefull information first in the documentation
public void setCacheNames(#Nullable
Collection cacheNames) Specify the set of cache names for this CacheManager's 'static' mode. The number
of caches and their names will be fixed after a call to this method,
with no creation of further cache regions at runtime.
Also
CaffeineCacheManager(String... cacheNames) Construct a static
CaffeineCacheManager, managing caches for the specified cache names
only.
So as you see, providing the cacheNames on the constructor means that this cacheManager will be able to handle only those cacheNames in the future. So you could not instruct that cacheManager to handle other cacheNames dynamically in the future.
You still need to pass the cacheNames through the annotation though
#Cacheable(cacheName = "example1", cacheManager = "mainCacheManager") as if you have multiple methods where this cacheManager is used it would make sense for those two methods to have their own cacheName each one.
#Cacheable(cacheName = "example1", cacheManager = "mainCacheManager")
public String method1( String a) {
....
}
#Cacheable(cacheName = "example2", cacheManager = "mainCacheManager")
public String method2( String b) {
....
}
Those 2 different cacheNames inform the cacheManager that there are 2 different namespaces for caches that it should handle and the annotation informs the cacheManager which cacheName relates to this method.

Spring caching and custom CacheResolver benefits/usage

Good evening all,
I'm new to Spring, let along Spring's caching. That said, I have a group of Maven projects all under the umbrella of a project, each of which requires some amount of caching. That said, my initial attempt at caching with multiple cache managers was failing... in some research I read some about using a custom CacheResolver and binding multiple cacheManagers within that. This thread spawned my interest.
Based on the answers there, I had some questions and was advised to post it in a new question. The gist of my question(s) is:
First off, is there an advantage to having multiple cacheManagers. For example on the project I'm working on, I originally thought we would need multiple managers, but I think if we were to put a singular manager in a -common maven project that all of our other projects use, then we could get away with the one (and it manages all of our caches (30+)).
Secondly, in the above example, let's assume I declare multiple cacheManagers in CustomCacheManager. I don't quite follow on how I determine which manager to use?
Thanks in advance!
Declare one cache manager as primary. Here is an example for reference:
#Configuration
#EnableCaching
#PropertySource(value = { "classpath:/cache.properties" })
public class CacheConfig {
#Bean
#Primary
public CacheManager hazelcastCacheManager() {
ClientConfig config = new ClientConfig();
HazelcastInstance client = HazelcastClient.newHazelcastClient(config);
return new HazelcastCacheManager(client);
}
#Bean
public CacheManager guavaCacheManager() {
GuavaCacheManager cacheManager = new GuavaCacheManager("mycache");
CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(10, TimeUnit.MINUTES);
cacheManager.setCacheBuilder(cacheBuilder);
return cacheManager;
}
}
Then specify it class level:
#Service
#CacheConfig(cacheManager="hazelcastCacheManager")
public class PolicyServiceImpl implements PolicyService {
}
You can also put it to method level:
#Service
public class PolicyServiceImpl implements PolicyService {
#Override
#Cacheable(value = "POLICY_", key = "#id", cacheManager= "guavaCacheManager")
public Policy getPolicyDetails(int id) {
return new Policy(id, "IX4546");
}
}
Also another way of mentioning at method level or class level would be:
#CacheConfig(cacheManager = "ehCacheManager")
#Target(value = ElementType.TYPE)
#Retention(value = RetentionPolicy.RUNTIME)
public #interface EhCacheable {
}
#EhCacheable
#Service
public class PolicyServiceImpl implements Policy {
}
You can also refer this link for custom cache resolver: https://github.com/isaolmez/spring-cache-samples/tree/master/spring-cache-custom

How to have multiple cache manager configuration in multiple modules/projects spring cache java

Have two different modules currently Let say Project A and Project B. Project B imported/used into/in Project A. Currently Project B already have CacheManager.
Project B
public class CacheConfig {
#Bean
public CacheManager cacheManager() {
// using SimpleCacheManager()
}
}
But now planed to implement CacheManager in Project A for someother Purpose.
class SomeCacheConfig{
#Bean
public CacheManager someCacheManager(){
// using SimpleCacheManager()
}
}
While loading application throws below exception.
java.lang.IllegalStateException: No CacheResolver specified, and no unique bean of type CacheManager found. Mark one as primary (or give it the name 'cacheManager') or declare a specific CacheManager to use, that serves as the default one.
Can you please help me how to achieve multiple cacheManager in multiple modules/projects.
ok then.
put #Primary on the CacheManager bean that will use as default.
#Primary
#Bean(name = "primaryCacheManager")
public CacheManager primaryCacheManager() {
return new SimpleCacheManager();
}
#Bean(name = "myCacheManager")
public CacheManager myCacheManager() {
return new SimpleCacheManager();
}
and when you want to use another one(i.e. not a default), explictly define a name of CacheManager bean with #Qualifier annotation.
#Autowired
#Qualifier("myCacheManager")
private CacheManager myCacheManager;
or if you use annotation base Spring Cache implementation, you can also define a CacheManager name as property of those annotations
#Cacheable(value = "some",cacheManager = "myCacheManager")
public String getSome(){
return "";
}
You can use the CompositeCacheManager implementation provided by Spring (https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/support/CompositeCacheManager.html)
This allows you to compose a list of cache managers. The composite manager will iterate through the list and get the cache in the first manager it exists in. Please note that "Note: Regular CacheManagers that this composite manager delegates to need to return null from getCache(String) if they are unaware of the specified cache name, allowing for iteration to the next delegate in line. However, most CacheManager implementations fall back to lazy creation of named caches once requested; check out the specific configuration details for a 'static' mode with fixed cache names, if available."
What eventually worked for me as Erik Ahlswede suggested
#Bean
public CacheManager cacheManager() {
return new CompositeCacheManager(
new ConcurrentMapCacheManager("cacheA") {
#Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder()
.expireAfterWrite(CACHE_TTL_IN_SECONDS, TimeUnit.SECONDS)
.maximumSize(MAX_ENTRIES_IN_CACHE)
.build().asMap(), false);
}
},
new ConcurrentMapCacheManager("cacheB") {
#Override
protected Cache createConcurrentMapCache(final String name) {
return new ConcurrentMapCache(name,
CacheBuilder.newBuilder()
.expireAfterWrite(CACHE_TTL_IN_SECONDS, TimeUnit.SECONDS)
.maximumSize(MAX_ENTRIES_IN_CACHE)
.build().asMap(), false);
}
}
);
}
And then use it with
#Cacheable(cacheNames = "someComplicatedAction", cacheManager = "cacheA")
public String someComplicatedAction() {
}

Spring #Cacheable annotation for same method in different service

I have implemented the standard redis caching template in a Spring boot application as per the following article:
What I have is two different services that get a list of objects:
#RequestMapping("/admin/test/list")
public String testCache() {
List<Cocktail> cocktails = cocktailsService.list();
List<Ingredient> ingredients = ingredientsService.list();
return "index";
}
Note: that the method name and signature is the same (i.e. list() ) but they both have different cache names as such:
// CocktailService
#Cacheable(value = “COCKTAILS”)
public List<Cocktail> list() {
return repository.findAll();
}
// IngredientsService
#Cacheable(value = “INGREDIENTS”)
public List<Ingredient> list() {
return repository.findAll();
}
The Problem
Even thou the cache name is different the method is always returning the list from the cache as there is no distinction at method level when generating the keys.
Possible Solutions
I know three solutions could be:
Change the method name
Write a custom KeyGenerator
Set Cache SpEL to make use of #root.target such as:
#Cacheable(value=”COCKTAILS”, key="{#root.targetClass}")
#Cacheable(value=”INGREDIENTS”, key="{#root.targetClass}")
Question
But surly there must be a better way or not?
There is an issue in the article you've followed. When creating your CacheManager bean, you need to invoke cacheManager.setUsePrefix(true);, only then the cache names COCKTAILS and INGREDIENTS will be used as Redis cache key discriminator.
Here is how you should declare your cache manager bean:
#Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
RedisCacheManager cacheManager = new RedisCacheManager(redisTemplate);
// Number of seconds before expiration. Defaults to unlimited (0)
cacheManager.setDefaultExpiration(300);
cacheManager.setUsePrefix(true);
return cacheManager;
}

Spring #Cacheable doesn't cache public methods

I have following method;
#Cacheable(value = "psgSiteToMap", key = "'P2M_'.concat(#siteName)")
public Map getSiteDetail(String siteName) {
Map map = new HashMap();
.....
//construct map variable here
.......
return map;
}
While project startup, cannot autowire class this method belongs to. If i change above method as following;
#Cacheable(value = "psgSiteToMap", key = "'P2M_'.concat(#siteName)")
private Map getSiteDetail(String siteName) {
Map map = new HashMap();
.....
//construct map variable here
................
return map;
}
public Map getSiteDetailPublic(String siteName) {
return this.getSiteDetail(siteName);
}
it works. Is there any restriction on #Cacheable annotation for public methods?
Thanks in advance
Spring AOP works only on public methods by default. You'd need AspectJ and load time or compile time weaving to make it work on private methods.
So it works in your case means that when you move the #Cacheable to the private method the proxy is not created at all and that works is autowireing, but not caching.
You probably have not set proxy-target-class property in your XML configuration or its equivalent annotation attribute. Can you please add the Spring configuration you're using and the class definition line. I'm interested if it implements any interfaces? Than I'll expand my answer with more details.

Categories

Resources