I've got a method in UserService:
#Cacheable(value="user", key="#p0")
public User find(String name) {
return userRepository.findOneByName(name);
}
And it caches. But then I try to get all keys from 'user' cache:
CacheManager cacheManager = CacheManager.getInstance();
cacheManager.getCache("user").getKeys().forEach(o -> log.debug(o.toString()));
Output:
com.cache.domain.User#1
Instead, for example, 'John Doe'.
See the Javadoc of getKeys
Returns a list of all elements in the cache, whether or not they are expired.
That's actually returning the elements, no the ids. You may want to change your code to cast o to Element and output getObjectKey() instead.
You don't need to specify the key attribute. Since what you want is to use the single argument of your method (name) the cache abstraction will use that by default.
Related
I want to modify a property of a participant which is an array of relationships during a transaction.
Let's assume I have a user that holds an array of keys like so:
participant User identified by userId {
o String userId
--> Key[] keys
}
asset Key identified by keyId {
o String keyId
}
transaction modifyUserKeys {
--> User user
}
Then in the transaction processor function I modify the array (as in adding and removing elements in it) and update the participant:
function modifyUserKeys(tx) {
let user = tx.user;
// create a new key via factory
var newKey = ...
user.keys.push(newKey);
return getParticipantRegistry('com.sample.User')
.then(function (participantRegistry) {
return participantRegistry.update(user);
});
}
In the documentation I saw a method called addArrayValue() which adds an element to an array. Now I'm unsure whether I'm meant to use this over conventional array manipulation like in my example.
What purpose does this addArrayValue() method have and am I able to e.g. remove elements from keys via keys.pop() or is it restricted to just the addition of new elements like the documentation suggests?
you can use conventional (push/pop) if you like (and as you've done on the array), but newKey would need to use newRelationship()
a useful example similar to what you're trying to achieve is here -> https://github.com/hyperledger/composer-sample-networks/blob/master/packages/fund-clearing-network/lib/clearing.js#L151 in Composer sample networks - addArrayValue() is also validating that it does not violate the model
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()]));
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).
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).
I have these to methods:
#Cacheable(value="products")
public Product findByName(String name)
#CacheEvict(value = "products", key="#productId")
public boolean updateProduct(int productID)
The product has a field id, which is the key.
Now I have the problem, that themethod findByName still find old objects after using the update-method. I think, the problem, is that findByName strored the object under the key name and not the productId. In the method arguments, I dont have the productId. But I dont know, how I can tell Spring cache to use a property of the returned object.
You cannot use a field of the returned object as the cache key. Only input parameters are valid for the key="#someFieldName" parameter.
If you have trouble with outdated objects, you may need to evict the whole cache after a product update with a differnt key type than the findByName method like this:
#CacheEvict(value = "products", allEntries = true)
This is fairly common limitation to run inte with the Spring-cache framework. The way I usually design around it, when it becomes an issue, is to include the required parameter in the call that should evict the cache, even if it's not needed. Like so:
#CacheEvict(value = "products", key="#productName")
/* productName only needed for CacheEvict... */
public boolean updateProduct(int productID, String productName) {
...
}