Spring Boot #CachePut value is null - java

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;
}

Related

Spring boot #Cacheable not working as expected with #Transactional

I am not able to share the actual code because of corporate policies but below is an example of method structures.
So in the example I want to the cache on the method in Class B to be cleared when the exception is thrown in class A.
NB: I can not move the cache to Class A so that is not a feasible solution.
I have tried reading all answers and posts online to get this working but not able to figure it out.
Please help with suggestions. A
I have set the following properties in application.properties
spring.cache.enabled=true
spring.cache.jcache.config=classpath:cache/ehcache.xml
#EnableCaching
#EnableTransactionManagement
Main Class{
#Autowired
CacheManager cacheManager
#PostConstruct
void postConstruct(){
(JCacheCacheManager)cachemanager).setTransactionAware(true);
}
}
#Service
Class A{
#Autowired
B b;
#Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
//TestCode to test cache clears if exception thrown here
throw new RuntimeException("test");
}
}
#Service
Class B{
#Cacheable("cacheName")
public List<Data> getDataFromSystem(String key){
client call code here
return dataList;
}
}
There should be other ways, but the following could be a valid solution.
The first step will be to define a custom exception in order to be able to handle it later as appropriate. This exception will receive, among others, the name of the cache and the key you want to evict. For example:
public class CauseOfEvictionException extends RuntimeException {
public CauseOfEvictionException(String message, String cacheName, String cacheKey) {
super(message);
this.cacheName = cacheName;
this.cacheKey = cacheKey;
}
// getters and setters omitted for brevity
}
This exception will be raised by your B class, in your example:
#Service
Class A{
#Autowired
B b;
#Transactional
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
// Sorry, because in a certain sense you need to be aware of the cache
// name here. Probably it could be improved
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
Now, we need a way to handle this kind of exception.
Independently of that way, the idea is that the code responsible for handling the exception will access the configured CacheManager and trigger the cache eviction.
Because you are using Spring Boot, an easy way to deal with it is by extending ResponseEntityExceptionHandler to provide an appropriate #ExceptionHandler. Please, consider read for more information the answer I provided in this related SO question or this great article.
In summary, please, consider for example:
#ControllerAdvice
public class CustomExceptionHandler extends ResponseEntityExceptionHandler {
#Autowired
private CacheManager cacheManager;
#ExceptionHandler(CauseOfEvictionException.class)
public ResponseEntity<Object> handleCauseOfEvictionException(
CauseOfEvictionException e) {
this.cacheManager.getCache(e.getCacheName()).evict(e.getCacheKey());
// handle the exception and provide the necessary response as you wish
return ...;
}
}
It is important to realize that when dealing with keys composed by several arguments by default (please, consider read this as well) the actual cache key will be wrapped as an instance of the SimpleKey class that contains all this parameters.
Please, be aware that this default behavior can be customized to a certain extend with SpEL or providing your own cache KeyGenerator. For reference, here is the current implementation of the default one provided by the framework, SimpleKeyGenerator.
Thinking about the problem, a possible solution could be the use of some kind of AOP as well. The idea will be the following.
First, define some kind of helper annotation. This annotation will be of help in determining which methods should be advised. For example:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EvictCacheOnError {
}
The next step will be defining the aspect that will handle the actual cache eviction process. Assuming you only need to advice Spring managed beans, for simplicity we can use Spring AOP for that. You can use either an #Around or an #AfterThrowing aspect. Consider the following example:
#Aspect
#Component
public class EvictCacheOnErrorAspect {
#Autowired
private CacheManager cacheManager;
#Around("#annotation(your.pkg.EvictCacheOnError)")
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (CauseOfEvictionException e) {
this.cacheManager.getCache(
e.getCacheName()).evict(e.getCacheKey()
);
// rethrow
throw e;
}
}
}
The final step would be annotate the methods in which the behavior should be applied:
#Service
Class A{
#Autowired
B b;
#Transactional
#EvictCacheOnError
public List<Data> getAllBusinessData(){
List<Data> dataList = b.getDataFromSystem("key");
throw new CauseOfEvictionException("test", "cacheName", "key");
}
}
You may even try generalizing the idea, by providing in the EvictCacheOnError annotation all the necessary information you need to perform the cache eviction:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface EvictCacheOnError {
String cacheName();
int[] cacheKeyArgsIndexes();
}
With the following aspect:
#Aspect
#Component
public class EvictCacheOnErrorAspect {
#Autowired
private CacheManager cacheManager;
#Autowired
private KeyGenerator keyGenerator;
#Around("#annotation(your.pkg.EvictCacheOnError)")
// You can inject the annotation right here if you want to
public void evictCacheOnError(ProceedingJoinPoint pjp) {
try {
Object retVal = pjp.proceed();
return retVal;
} catch (Throwable t) {
// Assuming only is applied on methods
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
// Obtain a reference to the EvictCacheOnError annotation
EvictCacheOnError evictCacheOnError = method.getAnnotation(EvictCacheOnError.class);
// Compute cache key: some safety checks are imperative here,
// please, excuse the simplicity of the implementation
int[] cacheKeyArgsIndexes = evictCacheOnError.cacheKeyArgsIndexes();
Object[] args = pjp.getArgs();
List<Object> cacheKeyArgsList = new ArrayList<>(cacheKeyArgsIndexes.length);
for (int i=0; i < cacheKeyArgsIndexes.length; i++) {
cacheKeyArgsList.add(args[cacheKeyArgsIndexes[i]]);
}
Object[] cacheKeyArgs = new Object[cacheKeyArgsList.size()];
cacheKeyArgsList.toArray(cacheKeyArgs);
Object target = pjp.getTarget();
Object cacheKey = this.keyGenerator.generate(target, method, cacheKeyArgs);
// Perform actual eviction
String cacheName = evictCacheOnError.cacheName();
this.cacheManager.getCache(cacheName).evict(cacheKey);
// rethrow: be careful here if using in it with transactions
// Spring will per default only rollback unchecked exceptions
throw new RuntimeException(t);
}
}
}
This last solution depends on the actual method arguments, which may not be appropriate if the cache key is based on intermediate results obtained within your method body.

When using ThreadLocal it always returns null, although when debbaged it shows that it is actually gets initialized with a value

I want first to mention that when I first implemented it, it was working fine, now it is not and I don't know what the things I changed that stopped it from working.
I am working on a Spring boot project that uses JPA and hibernate, I want to get the MetadataImplementor from Hibernate.
I created a class that catches it during initialization and put it in a ThreadLocal, here is the code that does that:
public class HibernateMetadata implements SessionFactoryBuilderFactory {
private static final ThreadLocal<MetadataImplementor> metadataImplementor = new ThreadLocal<>();
#Override
public SessionFactoryBuilder getSessionFactoryBuilder(MetadataImplementor metadataImplementor,
SessionFactoryBuilderImplementor defaultBuilder) {
HibernateMetadata.metadataImplementor.set(metadataImplementor);
return defaultBuilder;
}
public static MetadataImplementor getMetadataImplementor() {
return metadataImplementor.get();
}
}
This class always gets called during startup and the MetadataImplementor configured by hibernating gets put in the ThreadLocal, I am sure of that, I debugged it and it showed me it does this during startup.
It was working fine, now suddenly it stopped and it always returns null.
If I try to get the MetadataImplementor from within another service code as the following, it returns null:
#Override
public void introspect(Class<?> klass, Map<String, Property> properties) {
// ...
MetadataImplementor metadataImplementor = HibernateMetadata.getMetadataImplementor(); // this returns null
if (metadataImplementor == null) {
return; // it always returns null and return
}
PersistentClass persistentClass = metadataImplementor.getEntityBinding(klass.getName());
Iterator<?> propertyIterator = persistentClass.getPropertyClosureIterator();
while (propertyIterator.hasNext()) {
Property property = createProperty(klass, (org.hibernate.mapping.Property) propertyIterator.next(),
metamodelImplementor);
properties.put(property.getName(), property);
}
// ...
}
If I don't use the ThreadLocal and I directly use the metadataImplementor instead, it gets initialized and it works.
I think it's like it was calling it from another thread, I don't know, Can anyone point me towards what I am missing here?.

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 #Cacheable: Preserve old value on error

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.

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