Spring framework + Hazelcast , how to enable/disable cache using #Cacheable annotation - spring

I am using Spring framework and hazelcast cache to cache REST APi at service layer. The api I am caching has #Cacheable annotation with cachename and keygenerator which works fine. I am looking for best way to enable/disable caching using application property or consul property. For that I am trying to pass the property in condition attribute of #Cachable annotation but is not working. With this approach I will end up passing same value in multiple place (wherever I am caching at API level). Is there any good way to handle such operation.
As an example here is a code snippet
#Cacheable(cacheNames = CacheName.MyCache1,keyGenerator = "customKeyGen")
public CachingObject myFirstAPI(String param1, String param2) {
}
Here the hazelcast cache will use customKeyGen and put value (CachingObject) returned by myFirstAPI . If I have to disable this operation , my current approach is to pass some value (read from application property) as condition so that it evaluate the flag/condition before creating cache and cache the value only if the condition is true i.e. cache is enabled, e.g.
#Cacheable(cacheNames = CacheName.MyCache1,keyGenerator = "customKeyGen", condition="${enableCache}")
public CachingObject myFirstAPI(String param1, String param2) {
}
In my case the expression language I am passing in condition throwing exception , which I will figure out why (It is currently throwing SpelEvaluationException, Property or field 'enableCache' cannot be found on object of type 'org.springframework.cache.interceptor.CacheExpressionRootObject' )
My question is , is this correct way to enable/disable caching ? Please suggest.

Try spring.cache.type == none. See https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html#boot-features-caching-provider-none

Related

Cache key issues with Jcache

I am using JSR107 caching with Springboot. I have following method.
#CacheResult(cacheName = "books.byidAndCat")
public List<Book> getAllBooks(#CacheKey final String bookId, #CacheKey final BookCategory bookCat) {
return <<Make API calls and get actual books>>
}
First time it makes actual API calls, and second time it loads cache without issue. I can see the following part of log.
Computed cache key SimpleKey [cb5bf774-24b4-41e5-b45c-2dd377493445,LT] for operation CacheResultOperation[CacheMethodDetails ...
But the problem is I want to load cache without making even first API call, Simply needs to fill the cache like below.
String cacheKey = SimpleKeyGenerator.generateKey(bookId, bookCategory).toString();
cacheManager.getCache("books.byidAndCat").put(cacheKey, deviceList);
When I am checking, hashcode of cachekeys are same in both cases, But it is making API calls. If the hashcode is same in both cases, why it is making API calls without considering the cache ?
When debugging spring classes identified that, org.springframework.cache.interceptor.SimpleKeyGenerator is used with the cache key generation even #CacheResult is there.
EDIT and enhance the question :
Apart from that if getAllBooks has overloaded methods, and then call this cached method via separate overloaded method, in that case also method caching is not working.
I'm not an expert of JSR107 annotations in the context of Spring. I use the Spring Cache annotations instead.
When using JSR107, the key used is a GeneratedCacheKey. So that's what you should put in your cache. Not the toString() of it. Note that SimpleKeyGenerator isn't returning a GeneratedCacheKey. It returns a SimpleKey which is the key used by Spring when using its own cache annotations instead of JSR-107. For JSR-107, you need a SimpleGeneratedCacheKey.
Then, if you want to preload the cache, just call getAllBooks before needing it.
If you want to preload the cache in some other way, a #javax.cache.annotation.CachePut should do the trick. See its javadoc for an example.
As #Henri suggested, we can use the cacheput. But for that we need methods. With the below we can update the cache very similar to the cacheput,
//overloaded method both id and cat is available.
List<Object> bookIdCatCache = new ArrayList<>();
bookIdCatCache.add(bookId);
bookIdCatCache.add(deviceCat);
Object bookIdCatCacheKey = SimpleKeyGenerator.generateKey(bookIdCatCache.toArray(new Object[bookIdCatCache.size()]));
cacheManager.getCache("books.byidAndCat").put(bookIdCatCacheKey , bookListWithIdAndCat);
//overloaded method only id is there
List<Object> bookIdCache = new ArrayList<>();
String nullKey = null
bookIdCache.add(bookId);
bookIdCache.add(nullKey);
Object bookIdCacheKey = SimpleKeyGenerator.generateKey(bookIdCache.toArray(new Object[bookIdCache.size()]));
cacheManager.getCache("books.byidAndCat").put(bookIdCacheKey , bookListWithId);
//Not correct(My previous implementation)
String cacheKey = SimpleKeyGenerator.generateKey(bookId, bookCategory).toString();
//Correct(This is getting from spring)
Object cacheKey = SimpleKeyGenerator.generateKey(bookIdCatCache.toArray(new Object[bookIdCatCache.size()]));

How to not allow lazy loading from outside the transnational method?

I'm using JPA with Hibernate and Spring. I have an entity (Say Employee) with an attribute (Say of type Position) and this attribute is lazy-loaded.
I believe that when you try to access the position attribute, it will be lazy loaded from the DB and this is done inside the transnational method.
Let's say I didn't access the attribute in that transnational method. So if I tried to access it later, I would get "org.hibernate.LazyInitializationException: could not initialize proxy - no Session" which is normal because the session was closed by that transnational method.
At this point, I need it null (or not initialized) wherever I access it later in different method but this is not the case! The question is how can we make it null after committing and closing the session because it is not accessed while the session is open?
Below is a simple code to illustrate the issue.
// In some Service class
#Override
#Transactional(readOnly = true)
public Employee getEmployeeById(Integer id) throws Exception {
Employee emp = employeeDAO.getEmployeeById(id);
// I didn't access the position attribute here because I don't need it for now
return emp;
}
Later I call the above method (Say from some controller):
Employee emp = employeeService.getEmployeeById(904);
System.out.println(emp.getPosition()); // Here, the LazyInitializationException
//would occur, but I need this to be null or at least to prevent the lazy loading,
//thus, avoiding the exception. How?
I think this might be the answer that you're looking for
Hibernate - Avoiding LazyInitializationException - Detach Object From Proxy and Session
Basically
Use Hibernate to check if that field is initialised with Hibernate. isInitialized(fieldName)in the getter and return null if not initialised.
Inside employeeDAO.getEmployeeByIdmethod, create a new Employee object and set the parameters from the one that return from query, which is more work but prevent you to couple your domain to Hibernate.

Spring #Cacheable with filter

Every entity class has user.id value, I have filters on all services which filters data by principal.id and entity user.id on database level, simply adds where clause. I started to using #Cacheable spring option. But filters not works with spring-cache. How can I filter data from cache ?
#Override
#Cacheable(value = "countries")
public List<Country> getAll() {
return countryDao.findAll();
}
Different user has access to values other users if values are in cache.
From documentation
"As the name implies, #Cacheable is used to demarcate methods that are cacheable - that is, methods for whom the result is stored into the cache so on subsequent invocations (with the same arguments), the value in the cache is returned without having to actually execute the method."
In your case you don't have arguments therefore every time getAll is invoked it will return the cached version.
If your countryDao.findAll() inject the userid at database level, you have an issue as the first user calling countryDao.findAll() will cause his result to be cached, therefore other users will get the same result of the first user.
In general, if I understood how you designed the service, it is common that you don't inject the user at db level but pass it at service level so that this is decoupled from the current session (for example a web request).
However if you want to keep like that, it could still work by doing:
#Cacheable(value = "countries", key="#user.id")
public List<Country> getAll(User user) {
return countryDao.findAll();
}
All you have to do is pass the user to the method even if you don't use it explicitly (but the caching will).

#Cacheable : how to pass a new attribute that can be used in my own KeyGenerator?

I need your expertise :)
I'm working on a application where method calls on a service need to be authenticated.
That means I want each method call to be cached with a key containing the username (to avoid for an unauthorized user to retrieve information cached by an authorized one).
With a personnalized KeyGenerator, all works fine.
Example of my key : username:USERNAME.appVersion:VERSION.METHOD.PARAM1.etc
But at some location, I got methods that retrieve a national content : this one will be the same for each user. And I want to avoid a cache key for each user asking for this content.
Example : appVersion:VERSION.METHOD.PARAM1.etc
So when I'm positioning my #Cacheable annotations, is there any way to set a new parameter in it ? The Key Generator will be able to catch it and know if he had to prefix the cache key name with user information or not.
Thanks for your help :)
Take care
I don't really understand what you're saying by "set a new parameter in it". That parameter should come from somewhere right?
KeyGenerator gives you access to the Method, the actual instance and the method arguments. You may want to have a specific KeyGenerator for this particular cache operation which is something that will be available as from Spring 4.1 but in the mean time you can implement a composite that invokes the right KeyGenerator instance based on the method or, for instance, an annotation you have created to flag it.
Thank you snicoll, that was crystal clear and you really helped me a lot :)
Waiting for Spring 4.1, my team and I decided to use a custom #SharedCache annotation.
Here is some code samples to help if someone is in the same situation.
Given an existing custom GenericKeyGenerator (he's building a custom cache key for each cached method invocation)
We have a new custom AuthenticatedGenericKeyGenerator : he's inherited from GenericKeyGenerator and simply prefixing the cache key with user information
The application is now using AuthenticatedGenericKeyGenerator by default :
<cache:annotation-driven key-generator="keyGenerator"/>
<bean id="keyGenerator" class="your.package.AuthenticatedGenericKeyGenerator" />
AuthenticatedGenericKeyGenerator.java in details :
public class AuthenticatedGenericKeyGenerator extends GenericKeyGenerator {
public AuthenticatedGenericKeyGenerator() {
super(...);
}
#Override
public Object generate(final Object target, final Method method, final Object... params) {
String cacheKey = super.generate(target, method, params).toString();
if(!method.isAnnotationPresent(SharedCache.class)) {
cacheKey = "user:" + some user information + "." + cacheKey;
}
return cacheKey;
}
}
Our custom #SharedCache annotation :
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#Documented
public #interface SharedCache {
}
Now we just have to annotate #Cacheable methods with an extra #SharedCache if we want the cache key to be shared and not be unique (with an user id for example).

Groovy validation problem

I have a groovy system configured using tomcat and Oracle 10g.
I have a groovy class which defines an follows: (reduced version)
class ChangeTicket {
static constraints = {
chngNr(nullable:false)
}
String chngNr
}
My controller has defined a save method:
if (changeTicketInstance.validate() && !changeTicketInstance.hasErrors() && changeTicketInstance.save()) {
flash.message = "changeTicket.created"
...
}
As far as I know the save method calls by default the validate method in order to
know if the constraints are fullfilled or not therefore the validate method call is redundant. Anyway, when the save is performed an exception will be thrown if the field chngNr is NULL.
In fact the field cannot be empty (NULL) because I've defined the constraint (nullable:false).
What am I doing wrong here?
Thanks in advance,
Luis
The validate call should fail if chngNr is NULL. Some databases do not consider an empty string ("") null (HSQL). If you are binding chngNr to changeTicketInstance using params from a form it is getting assigned an empty string as a value and in that case you would want your constraint to be:
chngNr(blank:false)
Also, save() wont throw an Exception unless you use save(flush:true). Hibernate queues up the changes and, unless you flush, wont throw an actual exception.
try this:
chngName(blank:false,nullable:false)
:-)

Resources