Spring #Cacheable: Preserve old value on error - java

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.

Related

How to add SynchronizationCallbacks to #TransactionalEventListener during spring boot application startup?

I have a spring boot application that uses a few #TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT). I noticed that spring boot doesn't do any exception logging for them when they end up with an exception being thrown.
Because of this I wanted to add some generic logging facility for such exceptions. I found that TransactionalApplicationListener.SynchronizationCallback is the interface I need to implement. However it seems complicated to register these callbacks. I didn't find any call of TransactionalApplicationListener#addCallback in the spring dependencies that would achieve this.
Trying to get a list of TransactionalApplicationListener and the SynchronizationCallback injected and then call addCallback in a #PostConstruct didn't get me further because there were always no listeners injected even though the application did make successful use of them.
So how do I add SynchronizationCallbacks to TransactionalApplicationListeners during spring boot application startup?
The first thing to note is that TransactionalApplicationListeners like all ApplicationListener are not beans in the spring context. They live somewhat outside of it (see org.springframework.context.ConfigurableApplicationContext#addApplicationListener). So injecting them is not possible for the application context.
While debugging and looking through spring sources one finds that these listeners are being created by org.springframework.transaction.event.TransactionalEventListenerFactory. And that is where my solution steps into. We decorate that factory with another one that is aware of SynchronizationCallbacks:
public class SynchronizationCallbackAwareFactory implements EventListenerFactory, Ordered {
private final TransactionalEventListenerFactory delegate;
private final Provider<List<SynchronizationCallback>> synchronizationCallbacks;
private final int order;
public SynchronizationCallbackAwareFactory(TransactionalEventListenerFactory transactionalEventListenerFactory,
Provider<List<SynchronizationCallback>> synchronizationCallbacks,
int order) {
this.delegate = transactionalEventListenerFactory;
this.synchronizationCallbacks = synchronizationCallbacks;
this.order = order;
}
#Override
public boolean supportsMethod(Method method) {
return delegate.supportsMethod(method);
}
#Override
public ApplicationListener<?> createApplicationListener(String beanName, Class<?> type, Method method) {
ApplicationListener<?> applicationListener = delegate.createApplicationListener(beanName, type, method);
if (applicationListener instanceof TransactionalApplicationListener) {
TransactionalApplicationListener<?> listener = (TransactionalApplicationListener<?>) applicationListener;
Collection<SynchronizationCallback> callbacks = this.synchronizationCallbacks.get();
callbacks.forEach(listener::addCallback);
}
return applicationListener;
}
#Override
public int getOrder() {
return order;
}
}
Note that I use a javax.inject.Provider in my case to make the retrieval of the callbacks at the latest possible time.
The decorator has to be Ordered because spring will use the first factory supporting the method it gets across. And therefore the order of an instance of this class has to have higher precedence as the order value 50 of TransactionEventListenerFactory.
I had simmilar problem with code as below
#Transactional(propagation = Propagation.REQUIRES_NEW)
public class SomeListenerFacade {
#TransactionalEventListener
public void onSomething(SomeEvent event) {
throw new RuntimeException("some cause");
}
}
I followed your solution. It worked. On the way I've found an alternative way for at least seeing that exception in the logfile
# application.properties
logging.level.org.springframework.transaction.support.TransactionSynchronizationUtils = DEBUG

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 Boot #CachePut value is null

I have a map that stores simple POJOs, the key is the id field of the POJO.
From Spring's #CachePut annotation, I expected something like this:
JobModel jm = new JobModel();
cachemap.put(jm.getId(), jm);
Still, it inserts null values to the cache every time. If I disallow null values when I configure the Cache, I get en exception saying null is being inserted.
Code:
#SpringBootApplication
#EnableScheduling
#EnableAutoConfiguration
#EnableCaching
public class Application {
public static void main(String[] args) {
applicationContext = SpringApplication.run(Application.class, args);
}
...
private GuavaCache jobCache() {
return new GuavaCache(CacheNames.CACHE_JOB, CacheBuilder.newBuilder()
.maximumSize(999999)
.build(),
false);// or true, still null values
}
#Bean(name = "cacheManager")
public CacheManager cacheManager() {
SimpleCacheManager simpleCacheManager = new SimpleCacheManager();
simpleCacheManager.setCaches(Arrays.asList(
jobCache(),....
));
return simpleCacheManager;
}
And the Dao class, implementing an interface, the Annotations are declared in the Dao's (the class that implements the interface, not in the interface itself)
#CachePut(value = CacheNames.CACHE_JOB,key = "jm.id")
#Transactional
#Override
public void insert(JobModel jm) {...}
I tried jm.id, jm.getid(), #a0.getId(). Still, everytime I get and exception from com.google.common.base.Preconditions.checkNotNull() (or just a simple null insert). I placed a breakpoint there and I can see that the key is what I expected it to be (a guid, string), but the value is null.
Per the spring docs at http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/cache/annotation/CachePut.html
it always causes the method to be invoked and its result to be stored in the associated cache.
Your insert method needs to return the value you want cached.
Could be something as simple as:
#CachePut(value = CacheNames.CACHE_JOB,key = "jm.id")
#Transactional
#Override
public JobModel insert(JobModel jm) {
return jm;
}
though this doesn't feel like the best way to design the interaction. You may want to look at moving the annotation to whatever method is constructing the JobModel passed to the insert method or if the JobModel is being read from a database, the method which saves the JobModel to the database may be a good place as well.
Also, you can use unless conditional property on the cache annotations as below. This parameter takes a SpEL expression that is evaluated to either true or false. If true, the method is cached - if not, it behaves as if the method is not cached.
#CachePut(value = CacheNames.CACHE_JOB, key = "jm.id", unless = "#result == null")
#Transactional
#Override
public void insert(JobModel jm) {
return jm;
}

How to build and utilize a cache using CacheBuilder in Java

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.

Spring cache for a given request

I am writing a web application using Spring MVC. I have a interface that looks like this:
public interface SubscriptionService
{
public String getSubscriptionIDForUSer(String userID);
}
The getSubscriptionIDForUser actually makes a network call to another service to get the subscription details of the user. My business logic calls this method in multiple places in its logic. Hence, for a given HTTP request I might have multiple calls made to this method. So, I want to cache this result so that repeated network calls are not made for the same request. I looked at the Spring documentation, but could not find references to how can I cache this result for the same request. Needless to say the cache should be considered invalid if it is a new request for the same userID.
My requirements are as follows:
For one HTTP request, if multiple calls are made to getSubscriptionIDForUser, the actual method should be executed only once. For all other invocations, the cached result should be returned.
For a different HTTP request, we should make a new call and disregard the cache hit, if at all, even if the method parameters are exactly the same.
The business logic might execute its logic in parallel from different threads. Thus for the same HTTP request, there is a possibility that Thread-1 is currently making the getSubscriptionIDForUser method call, and before the method returns, Thread-2 also tries to invoke the same method with the same parameters. If so, then Thread-2 should be made to wait for the return of the call made from Thread-1 instead of making another call. Once the method invoked from Thread-1 returns, Thread-2 should get the same return value.
Any pointers?
Update: My webapp will be deployed to multiple hosts behind a VIP. My most important requirement is Request level caching. Since each request will be served by a single host, I need to cache the result of the service call in that host only. A new request with the same userID must not take the value from the cache. I have looked through the docs but could not find references as to how it is done. May be I am looking at the wrong place?
I'd like to propose another solution that a bit smaller than one proposed by #Dmitry. Instead of implementing own CacheManager we can use ConcurrentMapCacheManager provided by Spring in 'spring-context' artifact. So, the code will look like this (configuration):
//add this code to any configuration class
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager cacheManager() {
return new ConcurrentMapCacheManager();
}
and may be used:
#Cacheable(cacheManager = "cacheManager", cacheNames = "default")
public SomeCachedObject getCachedObject() {
return new SomeCachedObject();
}
I ended up with solution as suggested by herman in his comment:
Cache manager class with simple HashMap:
public class RequestScopedCacheManager implements CacheManager {
private final Map<String, Cache> cache = new HashMap<>();
public RequestScopedCacheManager() {
System.out.println("Create");
}
#Override
public Cache getCache(String name) {
return cache.computeIfAbsent(name, this::createCache);
}
#SuppressWarnings("WeakerAccess")
protected Cache createCache(String name) {
return new ConcurrentMapCache(name);
}
#Override
public Collection<String> getCacheNames() {
return cache.keySet();
}
public void clearCaches() {
cache.clear();
}
}
Then make it RequestScoped:
#Bean
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public CacheManager requestScopedCacheManager() {
return new RequestScopedCacheManager();
}
Usage:
#Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
Update:
After a while, I have found that my previous solution was incompatible with Spring-actuator. CacheMetricsRegistrarConfiguration is trying to initialize request scoped cache outside the request scope, which leads to exception.
Here is my alternative Implementation:
public class RequestScopedCacheManager implements CacheManager {
public RequestScopedCacheManager() {
}
#Override
public Cache getCache(String name) {
Map<String, Cache> cacheMap = getCacheMap();
return cacheMap.computeIfAbsent(name, this::createCache);
}
protected Map<String, Cache> getCacheMap() {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
if (requestAttributes == null) {
return new HashMap<>();
}
#SuppressWarnings("unchecked")
Map<String, Cache> cacheMap = (Map<String, Cache>) requestAttributes.getAttribute(getCacheMapAttributeName(), RequestAttributes.SCOPE_REQUEST);
if (cacheMap == null) {
cacheMap = new HashMap<>();
requestAttributes.setAttribute(getCacheMapAttributeName(), cacheMap, RequestAttributes.SCOPE_REQUEST);
}
return cacheMap;
}
protected String getCacheMapAttributeName() {
return this.getClass().getName();
}
#SuppressWarnings("WeakerAccess")
protected Cache createCache(String name) {
return new ConcurrentMapCache(name);
}
#Override
public Collection<String> getCacheNames() {
Map<String, Cache> cacheMap = getCacheMap();
return cacheMap.keySet();
}
public void clearCaches() {
for (Cache cache : getCacheMap().values()) {
cache.clear();
}
getCacheMap().clear();
}
}
Then register a not(!) request scoped bean. Cache implementation will get request scope internally.
#Bean
public CacheManager requestScopedCacheManager() {
return new RequestScopedCacheManager();
}
Usage:
#Cacheable(cacheManager = "requestScopedCacheManager", cacheNames = "default")
public YourCachedObject getCachedObject(Integer id) {
//Your code
return yourCachedObject;
}
EHCache comes to mind right off the bat, or you could even roll-your-own solution to cache the results in the service layer. There are probably a billion options on caching here. The choice depends on several factors, like do you need the values to timeout, or are you going to clean the cache manually. Do you need a distributed cache, like in the case where you have a stateless REST application that is distributed amongst several app servers. You you need something robust that can survive a crash or reboot.
You can use Spring Cache annotations and create your own CacheManager that caches at request scope. Or you can use the one I wrote: https://github.com/rinoto/spring-request-cache

Categories

Resources