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;
}
Related
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.
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")
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() {
}
I have a method that pulls in a bunch of data. This has the potential to take a decent amount of time due to the large data set and the amount of computation required. The method that does this call will be used many times. The result list should return the same results each time. With that being said, I want to cache the results, so I only have to do that computation once. I'm supposed to use the CacheBuilder class. The script I have is essentially something like:
class CheckValidValues implements AValidValueInterface {
private ADataSourceInterface dataSource;
public CheckValidValues(ADataSourceInterface dataSource) {
this.dataSource = dataSource;
}
#Override
public void validate(String value) {
List<?> validValues = dataSource.getValidValues();
if (!validValues.contains(value)) {
// throw an exception
So I'm not even sure where I should be putting the caching method (i.e. in the CheckValidValues class or the getValidValues() method in dataSource. Also, I'm not entirely sure how you can add code into one of the methods without it instantiating the cache multiple times. Here's the route that I'm trying to take, but have no idea if it's correct. Adding above the List validValues = dataSource.getValidValues() line:
LoadingCache<String, List<?>> validValuesCache = CacheBuilder.newBuilder()
.expireAfterAccess(30, TimeUnit.SECONDS)
.build(
new CacheLoader<String, List<?>>() {
public List<?> load(#Nonnull String validValues) {
return valuesSupplier.getValidValues();
}
}
);
Then later, I'd think I could get that value with:
validValuesCache.get("validValues");
What I think should happen there is that it will do the getValidValues command and store that in the cache. However, if this method is being called multiple times, then, to me, that would mean it would create a new cache each time.
Any idea what I should do for this? I simply want to add the results of the getValidValues() method to cache so that it can be used in the next iteration without having to redo any computations.
You only want to cache a single value, the list of valid values. Use Guavas' Suppliers.memoizeWithExpiration(Supplier delegate, long duration, TimeUnit unit)
Each valid value is only existing once. So your List is essentially a Set. Back it by a HashSet (or a more efficient variant in Guava). This way the contains() is a hash table lookup instead of a sequential search inside the list.
We use Guava and Spring-Caching in a couple of projects where we defined the beans via Java configuration like this:
#Configuration
#EnableCaching
public class GuavaCacheConfig {
...
#Bean(name="CacheEnabledService")
public SomeService someService() {
return new CacheableSomeService();
}
#Bean(name="guavaCacheManager")
public CacheManager cacheManager() {
// if different caching strategies should occur use this technique:
// http://www.java-allandsundry.com/2014/10/spring-caching-abstraction-and-google.html
GuavaCacheManager guavaCacheManager = new GuavaCacheManager();
guavaCacheManager.setCacheBuilder(cacheBuilder());
return guavaCacheManager;
}
#Bean(name = "expireAfterAccessCacheBuilder")
public CacheBuilder<Object, Object> cacheBuilder() {
return CacheBuilder.newBuilder()
.recordStats()
.expireAfterAccess(5, TimeUnit.SECONDS);
}
#Bean(name = "keyGenerator")
public KeyGenerator keyGenerator() {
return new CustomKeyGenerator();
}
...
}
Note that the code above was taken from one of our integration tests.
The service, which return values should be cached is defined as depicted below:
#Component
#CacheConfig(cacheNames="someCache", keyGenerator=CustomKeyGenerator.NAME, cacheManager="guavaCacheManager")
public class CacheableService {
public final static String CACHE_NAME = "someCache";
...
#Cacheable
public <E extends BaseEntity> E findEntity(String id) {
...
}
...
#CachePut
public <E extends BaseEntity> ObjectId persist(E entity) {
...
}
...
}
As Spring-Caching uses an AOP approach, on invoking a #Cacheable annotated method Spring will first check if already a previous stored return value is available in the cache for the invoked method (depending on the cache key; we use a custom key generator therefore). If no value is yet available, Spring will invoke the actual service method and store the return value into the local cache which is available on subsequent calls.
#CachePut will always execute the service method and put the return value into the cache. This is useful if an existing value inside the cache should be replaced by a new value in case of an update for example.
I am planning to use the Spring #Cacheable annotation in order to cache the results of invoked methods.
But this implementation somehow does not look very "safe" to me. As far as I understand, the returned value will be cached by the underlying caching engine and will be deleted when the Spring evict method is called.
I would need an implementation which does not destroy the old value until the new value was loaded. This would be required and the following scenario should work:
Cacheable method is called -> Valid result returned
Result will be cached by the Spring #Cacheable backend
Spring invalidates cache because it expired (e.g. TTL of 1 hour)
Cacheable method is called again -> Exception/null value returned!
OLD result will be cached again and thus, future invokations of the method will return a valid result
How would this be possible?
Your requirement of serving old values if the #Cacheable method throws an exception can easily be achieved with a minimal extension to Google Guava.
Use the following example configuration
#Configuration
#EnableWebMvc
#EnableCaching
#ComponentScan("com.yonosoft.poc.cache")
public class ApplicationConfig extends CachingConfigurerSupport {
#Bean
#Override
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
GuavaCache todoCache = new GuavaCache("todo", CacheBuilder.newBuilder()
.refreshAfterWrite(10, TimeUnit.MINUTES)
.maximumSize(10)
.build(new CacheLoader<Object, Object>() {
#Override
public Object load(Object key) throws Exception {
CacheKey cacheKey = (CacheKey)key;
return cacheKey.method.invoke(cacheKey.target, cacheKey.params);
}
}));
simpleCacheManager.setCaches(Arrays.asList(todoCache));
return simpleCacheManager;
}
#Bean
#Override
public KeyGenerator keyGenerator() {
return new KeyGenerator() {
#Override
public Object generate(Object target, Method method, Object... params) {
return new CacheKey(target, method, params);
}
};
}
private class CacheKey extends SimpleKey {
private static final long serialVersionUID = -1013132832917334168L;
private Object target;
private Method method;
private Object[] params;
private CacheKey(Object target, Method method, Object... params) {
super(params);
this.target = target;
this.method = method;
this.params = params;
}
}
}
CacheKey serves the single purpose of exposing SimpleKey attributes. Guavas refreshAfterWrite will configure the refresh time without expiring the cache entries. If the methods annotated with #Cacheable throws an exception the cache will continue to serve the old value until evicted due to maximumSize or replaced by a new value from succesful method response. You can use refreshAfterWrite in conjunction with expireAfterAccess and expireAfterAccess.
I may be wrong in my reading of the Spring code, notably org.springframework.cache.interceptor.CacheAspectSupport#execute(org.springframework.cache.interceptor.CacheOperationInvoker, org.springframework.cache.interceptor.CacheAspectSupport.CacheOperationContexts), but I believe the abstraction does not provide what you ask indeed.
Spring will not expire entries, this will be left to the underlying caching implementation.
You mention that you would like to see values even though they are expired. That's against the expiry abstraction used in most cache implementations that I know of.
Returning a previously cached value on invocation error is clearly use case specific. The Spring abstraction will simply throw the error back at the user. The CacheErrorHandler mechanism only deals with cache invocation related exceptions.
All in all, it seems to me that what you are asking for is very use case specific and thus not something an abstraction would/should offer.