CacheEvict from Iterable collection - spring

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!

Related

Is double saving a new entity instance with a Spring data 2 JpaRepository correct?

I have two entities in a bi-directional many to many relationship.
A <-> many to many <-> B
I have an endpoint where a client can create an instance of A, and at the same time add some number of B entities to that A, by passing in an array of B entity id keys. Please keep in mind that these B entities already exist in the database. There is no business or software design case for tightly coupling their creation to the creation of A.
So class A looks like this, and B is the same, but with references to A.
#Entity
class A {
#Id
#GeneratedValue
int id;
#ManyToMany
List<B> bs;
String someValue;
int someValue2;
// With some getters and setters omitted for brevity
}
So at first try my endpoint code looks like this.
public A createA(#RequestBody A aToCreate) {
A savedA = aRepository.save(aToCreate);
savedA.getbs().forEach(b -> Service.callWithBValue(b.getImportantValue());
}
And the client would submit a JSON request like this to create a new A which would contain links to B with id 3, and B with id 4.
{
"bs": [{id:3}, {id:10}],
"someValue": "not important",
"someValue2": 1
}
Okay so everything's working fine, I see all the fields deserializing okay, and then I go to save my new A instance using.
aRepository.save(aToCreate);
And that works great... except for the fact that I need all the data associated with the b entity instances, but the A object returned by aRepository.save() has only populated the autofill fields on A, and done nothing with the B entities. They're still just hollow entities who only have their ids set.
Wut.
So I go looking around, and apparently SimpleJpaRepository does this.
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
And since the A entity is brand new, it only persists the A entity, but it doesn't merge it so I don't get any of the rich B data. So okay, if I modify my code to take this into account I get this.
public A createA(#RequestBody A aToCreate) {
A savedA = aRepository.save(aRepository.save(aToCreate));
savedA.getbs().forEach(b -> Service.callWithBValue(b.getImportantValue());
}
Which works just fine. The second pass through the repository service it merges instead of persists, so the B relationships get hydrated.
My question is: Is this correct, or is there something else I can do that doesn't look so ineloquent and awful?
To be clear this ONLY matters when creating a brand new instance of A, and once A is in the database, this isn't an issue anymore because the SimpleJpaRepository will flow into the em.merge() line of code. Also I have tried different CascadingType annotations on the relationship but none of them are what I want. Cascading is about persisting the state of the parent entity's view of its children, to its children, but what I want to do is hydrate the child entities on new instance creation, instead of having to make two trips to the database.
In the case of a new A, aToCreate and savedA are the same instance because that is what the JPA spec madates:
https://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#persist(java.lang.Object)
Make an instance managed and persistent.
Spring Data simply returns the same instance so persist/merge can be abstracted into one method.
If the B instances you wish to associate with A are existing entities then you need to fetch a reference to these existing instances and set them on A. You can do this without a database hit by using the T getOne(ID id) method of Spring Data's JpaRepository:
https://docs.spring.io/spring-data/jpa/docs/2.1.4.RELEASE/api/
You can do this in your controller or possibly via a custom deserializer.
This is what I ended up going with. This gives the caller the ability to save and hydrate the instance in one call, and explains what the heck is going on. All my Repository instances now extend this base instance.
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
/**
* Saves an instance twice so that it's forced to persist AND then merge. This should only be used for new detached entities that need to be saved, and who also have related entities they want data about hydrated into their object.
*/
#Transactional
default T saveAndHydrate(T save) {
return this.save(this.save(save));
}
}

How to execute save operation immediatly in a #Transactional method

Say I have following code snippet
#Transactional
public void doSthing(){
// save an enetity to db
SomeClass entityA = new entityA();
mapper.save(entityA);
// I got null here!
Integer id = entityA.getId();
anotherEntity.setVal(id);
otherMapper.upate(anotherEntity)
}
as u see, I need the entityA's id to update another entity, but it's null at that time, if I remove the #Transactional it works, but I want the two operations in tansaction, which mean that i need spring rollback the doSthing() method on any opereation failured.
By default, methods annotated with #Transactional will rollback on any RuntimeException. So you can achieve the rollback by throwing some runtime exception under some condition.
If you want to rollback on any exception just add the following:
#Transactional(rollbackFor=Exception.class)
But what #Delinum said in the comment is true in general, that is, if you invoke a save on a dao/repository it should assign an #Id to the value object that you are saving, making it an entity.
I don't know what is type of your 'mapper' instance, but some implementations could work in a way that when you call save it doesn't change the original object, but rather it returns the persisted object. So instead of this:
mapper.save(entityA);
// I got null here!
Integer id = entityA.getId();
Use this:
Integer id = mapper.save(entityA).getId();

Check if User object exists in #DBRef List<User>

I'm making use of MongoDB, Spring Data and Spring MVC.
I have a user model which has a list of contacts:
class User {
#DBRef
private List<User> contacts = new ArrayList<User>();
public List<User> getContacts() {
return contacts;
}
}
I currently have 4 users inside my database. 1 user has a particular contact (who is referred to the same collection by id).
Now, I want to check whether a user has a particular contact. I use the following code:
User userLoggedIn = userService.getLoggedInUser(); //user object
User contact = userService.findById(contactId); //contact
if(userLoggedIn.getContacts().contains(contact)) {
System.out.println("Has this contact.");
}
This output message is not shown. However, if I print the list of contacts of the user and their id's, I clearly see that the contact is inserted inside the list of the user.
I noticed that if I print the hashCode of the contact object and the one that is inside the list, I get a different value, so I assume that even though the details are the same, the object itself isn't.
How can I approach this problem by simply checking whether he is inside the list. Or should I just compare by id?
Otherwise stated: how can I check whether an object exists in the contacts list?
You should override an equals method in User.
From JavaDoc:
boolean contains(Object o)
Returns true if this list contains the specified element. More
formally, returns true if and only if this list contains at least one
element e such that (o==null ? e==null : o.equals(e)).
With equals you must override and hashCode
http://docs.jboss.org/hibernate/core/4.0/manual/en-US/html/persistent-classes.html#persistent-classes-equalshashcode

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

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

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

Resources