Can somebody exlain, with a sample, how works the cache annotation in play framework 2 in java
I would like to cache the result of the method with his parameters; something like this:
#Cache(userId, otherParam)
public static User getUser(int userId, String otherParam){
return a User from dataBase if it isn't in cache.
}
Maybe a tutorial is available?
Thanks for your help.
The #Cached annotation doesn't work for every method call. It only works for Actions and moreover, you can't use parameters as a cache key (it's only a static String). If you want to know how it works, look at the play.cache.CachedAction source code.
Instead, you will have to use either Cache.get(), check if result is null and then Cache.set() or the Cache.getOrElse() with a Callable with a code like :
public static User getUser(int userId, String otherParam){
return Cache.getOrElse("user-" + userId + "-" + otherParam, new Callable<User>() {
#Override
public User call() throws Exception {
return getUserFromDatabase(userId, otherParam);
}
}, DURATION);
}
Be careful when you construct your cache keys to avoid naming-collision as they are shared across the whole application.
Related
I'm trying to migrate my project to Quarkus Reactive with Hibernate Reactive Panache and I'm not sure how to deal with caching.
My original method looked like this
#Transactional
#CacheResult(cacheName = "subject-cache")
public Subject getSubject(#CacheKey String subjectId) throws Exception {
return subjectRepository.findByIdentifier(subjectId);
}
The Subject is loaded from the cache, if available, by the cache key "subjectId".
Migrating to Mutiny would look like this
#CacheResult(cacheName = "subject-cache")
public Uni<Subject> getSubject(#CacheKey String subjectId) {
return subjectRepository.findByIdentifier(subjectId);
}
However, it can't be right to store the Uni object in the cache.
There is also the option to inject the cache as a bean, however, the fallback function does not support to return an Uni:
#Inject
#CacheName("subject-cache")
Cache cache;
//does not work, cache.get function requires return type Subject, not Uni<Subject>
public Uni<Subject> getSubject(String subjectId) {
return cache.get(subjectId, s -> subjectRepository.findByIdentifier(subjectId));
}
//This works, needs blocking call to repo, to return response wrapped in new Uni
public Uni<Subject> getSubject(String subjectId) {
return cache.get(subjectId, s -> subjectRepository.findByIdentifier(subjectId).await().indefinitely());
}
Can the #CacheResult annotations be used with Uni / Multi and everything is handled under the hood correctly?
Your example with a #CacheResult on a method that returns Uni should actually work. The implementation will automatically "strip" the Uni type and only store the Subject in the cache.
The problem with caching Unis is that depending on how this Uni is created, multiple subscriptions can trigger some code multiple times. To avoid this you have to memoize the Uni like this:
#CacheResult(cacheName = "subject-cache")
public Uni<Subject> getSubject(#CacheKey String subjectId) {
return subjectRepository.findByIdentifier(subjectId)
.memoize().indefinitely();
}
This will ensure that every subscription to the cached Uni will always return the same value (item or failure) without re-executing anything of the original Uni flow.
I just want to disable cache for users that are admins. So I write a method to generate keys as below that returns null for admins. But I get
java.lang.IllegalArgumentException: Null key returned for cache
operation
exeption.
Is there any way achieve that?
//a method that generates a menu for each user
#Cacheable(cacheNames = "topmenu", keyGenerator = "uiComponentKey")
#Override
public String renderResponse() {...}
//method used by a key generator to generate cache keys.
#Override
public Object getCacheKey() {
if (user.isAdmin()) {
return null;
}
return user.getUser().getLogin() + "#" + "topmenu";
}
I guess you can achive that using conditional caching feature. Smth like this:
#Cacheable(cacheNames = "topmenu", condition="#user.isAdmin()")
#Override
public String renderResponse(User user) {...}
Note, that you're going to have to pass user object to this method in this case.
I am caching some object using Spring cache implementation, the underline cache is EhCache. I am trying to evict the cache based on wildcard search for the keys,the reason is the way I stored them and I only know the partial key. Hence I wanted to do something like below. I did search this forum for relevant answer but did not find any.
#CacheEvict(beforeInvocation=true, key="userId+%")
public User getUser(String userId)
{
//some implementation
}
Now if I try this I get an error for the SPEL. Also I tried to create a custom keygenerator for this, here the eviction works if the key generator returns one key, but I have a couple of keys based on my search.
#CacheEvict(beforeInvocation=true, keyGenerator="cacheKeyEvictor")
public User getUser(String userId)
{
//some implementation
}
//Custom key generator for eviction
public class cacheKeyEvictor implements KeyGenerator {
#Override
public Object generate(Object arg0, Method arg1, Object... arg2) {
//loop the cache and do a like search and return the keys
return object; //works if I send one key. Won't work for a list of keys
}
}
Any help on this is appreciated.
We have a lot of code in our code base that's similar to the following interface:
public interface SomethingService {
#Cacheable(value = "singleSomething")
Optional<Something> fetchSingle(int somethingId);
// more methods...
}
This works fine as long we're only using local caches. But as soon as we're using a distributed cache like Hazelcast, things start to break because java.util.Optional<T> is not serializable and thus cannot be cached.
With what I've come up so far to solve this problem:
Removing java.util.Optional<T> from the method definitions and instead checking for the trusty null.
Unwrapping java.util.Optional<T> before caching the actual value.
I want to avoid (1) because it would involve a lot of refactoring. And I have no idea how to accomplish (2) without implementing my own org.springframework.cache.Cache.
What other options do I have? I would prefer a generic (Spring) solution that would work with most distributed caches (Hazelcast, Infinispan, ...) but I would accept a Hazelcast-only option too.
A potential solution would be to register a serializer for the Optional type. Hazelcast has a flexibile serialization API and you can register a serializer for any type.
For more information see the following example:
https://github.com/hazelcast/hazelcast-code-samples/tree/master/serialization/stream-serializer
So something like this:
public class OptionalSerializer implements StreamSerializer<Optional> {
#Override
public void write(ObjectDataOutput out, Optional object) throws IOException {
if(object.isPresent()){
out.writeObject(object.get());
}else{
out.writeObject(null);
}
}
#Override
public Optional read(ObjectDataInput in) throws IOException {
Object result = in.readObject();
return result == null?Optional.empty():Optional.of(result);
}
#Override
public int getTypeId() {
return 0;//todo:
}
#Override
public void destroy() {
}
}
However the solution isn't perfect because this Optional thing will be part of the actual storage. So internally the Optional wrapper is also stored and this can lead to problems with e.g. queries.
I have a web application which uses Spring MVC. Is it possible that controller return temporary view depending on condition ? For example
#RequestMapping(value="/")
public String home(){
// some code here
return "home/{RANDOM_HASH}"
}
and user is redirected to this link. There is some action and when it finishes, he will be redirected somewhere else and he will not be able to connect to this even if he write full path, including random hash.
English isn’t my first language, so please excuse any mistakes.
Yes, you can use #PathVariable to bind HTTP parameters to method arguments:
#RequestMapping(value="home/{hash}")
public String link(#PathVariable String hash) {
// 1) verify hash was not made up
// 2) do whatever needs done with hash
return "somewhereElse";
}
As to how to verify the hash was really created by your application and not typed in the URL bar: you can create some sort of "token manager". It would manage all hashes: issue new tokens and invalidate old ones once they're used. Simplified implementation could be something like this:
#NotThreadSafe
class TokenService {
private final Set<String> hashes = new HashSet<String>();
public String getHash() {
String hash = "???"; // TODO: random generator
hashes.put(hash);
return hash;
}
public void invalidateHash(String hash) {
hashes.remove(hash);
}
public boolean checkHash(String hash) {
return hashes.contains(hash);
}
}
Please note that real-life implementation should make access to hashes thread-safe.