Spring Cache: How to use Cache key based on the response object - spring

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) {
...
}

Related

Spring Data JPA and Spring Web Updating Entities Avoiding Associations

I have the following entities:
Area
Listing
They are both many-to-many:
An area can have many listings
A listing can have many areas
Both Area and Listing have other fields like name, domain, etc.
I'm using Spring Web RestController as a way to update the entities.
For example:
#PutMapping("/{id}")
public Area update(#PathVariable Long id, #RequestBody Area update) {
return areaRepository.save(update);
}
However, as an Area can have many thousands of Listings, it's not practical to pass them all in the update request when I just want to update the Area name and other basic fields in my web application.
For example, the update json in the http request would be:
{
"id" : 69,
"name" : "San Francisco",
"domain" : "foo",
...
}
When serialised, the area instance above will have a listings field equal to null (obviously) and then when saved, all association are remove from this Area.
I'm thinking that I could do a select-then-update set of operations and only update the values necessary but that is cumbersome - especially when there are many dozens of non-association fields.
The question would be: how can I try to keep to the above code and http request and not remove all of the existing Listing associations when saving the input area? Is this possible? I want to update the name and other basic fields but not the association fields.
You can use the BeanUtilBean(org.apache.commons.beanutils.BeanUtilsBean).
Step 1: Create custom beanutilbean class.
#Component
public class CustomBeanUtilsBean extends BeanUtilsBean {
#Override
public void copyProperty(Object dest, String name, Object value)
throws IllegalAccessException, InvocationTargetException {
if(value==null)return;
super.copyProperty(dest, name, value);
}
}
Step 2: In your controller while updating. first get the Area from database & use copyProperties method as shown in below.
#PutMapping("/{id}")
public Area update(#PathVariable Long id, #RequestBody Area update) {
Area areaDB = areaRepository.findOne(update.getId());
customBeanUtilsBean.copyProperties(areaDB,update);//it will set not null field values of update to areaDB.
return areaRepository.save(areaDB);
}
Hope this will helps you..:)
Since you are using spring-data-jpa repository you can write a new method that takes the changed values of Area object with #Modifying and #Query annotations on it.
In the #Query you specify the HQL query with update statement as in here

Ehcache with Spring Cache assigns wrong key

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.

CacheEvict from Iterable collection

I have 2 method like this :
first, i'm get users deposit
#Override
#Transactional
#Cacheable(value = "deposits")
public Set<Deposit> getUserDeposit() {
User user = userRepository.findOneByUsername(
securityHolder.getUserDetails().getUsername());
Set<Deposit> deposits = user.getBalance().getDeposits();
return deposits;
}
and second, when save task entity which contain a one deposit as relationship i want evict from cache deposit by id :
(spring data interface)
#CacheEvict(value = "deposits", key = "#entity.deposit.id", condition = "#entity != null")
<S extends T> List<S> save(Iterable<S> entity);
but that no work.
CacheEvict works on an item not an iterator on items. There's a smell in your code: if you look at the SpEL expressions they are written as entity was the object to evict while its parameter type clearly says it's different.
There is another issue with the get. You are putting something in the cache with no key and the content depends on the connected user. You should make that information part of the key. Right now each new call overrides the content of the cache. Worse if you cache one user and then call that method with a different logged in user it'll get the deposit of another user!

#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).

Partial Entity Updates in WebAPI PUT/POST

Say you have a repository method to update a Document:
public Document UpdateDocument(Document document)
{
Document serverDocument = _db.Documents.Find(document.Id);
serverDocument.Title = document.Title;
serverDocument.Content = document.Content;
_db.SaveChanges();
return serverDocument;
}
In this case, the entity has two properties. When updating a Document, both of these properties are required in the JSON request, so a request to PUT /api/folder with a body of
{
"documentId" = "1",
"title" = "Updated Title"
}
would return an error because "content" was not provided. The reason I'm doing this is because, even for nullable properties and properties that the user doesn't update, it seems safer to force the client to specify these fields in the request to avoid overwriting unspecified fields with nulls serverside.
This has led me to the practice of always requiring every updatable property in PUT and POST requests, even if it means specifying null for those properties.
Is this cool, or is there a pattern/practice that I haven't learned about yet that might facilitate partial updates by sending only what is needed over the wire?
The best practice in API design is to use HTTP PATCH for partial updates.
In fact, use cases like yours are the very reason why IETF introduced it in the first place.
RFC 5789 defines it very precisely:
PATCH is used to apply partial modifications to a resource.
A new method is necessary to improve interoperability and prevent
errors. The PUT method is already defined to overwrite a resource
with a complete new body, and cannot be reused to do partial changes.
Otherwise, proxies and caches, and even clients and servers, may get
confused as to the result of the operation. POST is already used but
without broad interoperability (for one, there is no standard way to
discover patch format support).
Mark Nottingham has written a great article about the use of PATCH in API design - http://www.mnot.net/blog/2012/09/05/patch
In your case, that would be:
[AcceptVerbs("PATCH")]
public Document PatchDocument(Document document)
{
Document serverDocument = _db.Documents.Find(document.Id);
serverDocument.Title = document.Title;
serverDocument.Content = document.Content;
_db.SaveChanges();
return serverDocument;
}
Is this cool, or is there a pattern/practice that I haven't learned
about yet that might facilitate partial updates by sending only what
is needed over the wire?
A good practice of doing a POST or PUT is to only include values that you need for that specific request. In doing the UpdateDocument you should ask yourself what "really should be done here"? If you have a hundred fields on that object do you need to update all of them or only part of them. What "action" are you really trying to do?
Let's have an illustration for those questions, say we have a User object that has the following fields:
public class User {
public int Id {get;set;}
public string Username {get;set;}
public string RealName {get;set;}
public string Password {get;set;}
public string Bio {get;set;}
}
You then have two use cases:
Update the profile of a User
Update the password of a User
When you do each of those you will not, or it's a good idea to, have one update method that will do both. Instead of having a generic UpdateUser method you should have the following methods:
UpdateProfile
UpdatePassword
Methods that accepts fields that they just need, nothing more, nothing less.
public User UpdateProfile(int id, string username, string realname, string bio) {
}
public User UpdatePassword(int id, string password) {
}
Now comes the question:
I have a use case that a "user action" allows for an update on
multiple fields where some of the fields can have "no input" from the
user but I don't want to update that field in my model.
Suppose a user updates his/her profile and provided values for Username, RealName but not for Bio. But you do not want to set Bio as null or empty if it has a value already. Then that becomes a part of your application's business logic and that should be handled explicitly.
public User UpdateProfile(int id, string username, string realname, string bio) {
var user = db.Users.Find(id);
// perhaps a validation here (e.g. if user is not null)
user.Username = username;
user.RealName = realname;
if (!string.IsNullOrEmptyWHiteSpace(bio)) {
user.Bio = bio;
}
}

Resources