My team has two classes, User and Store, related by JPA #ManyToMany annotations. The relevant code is below.
When creating a new User object and setting its stores, life is good. But we're seeing some unexpected behavior when trying to update User objects through our Struts 2 / Spring web UI. (These problems do not occur when running simple automated integration tests through JUnit).
Simply put, when saving a User, its stores collection is not updated -- the database shows no changes, and reloads of the User object in question shows it to have an unchanged set of stores.
The only solution that we have found to this problem -- and we don't like this solution, it makes all of the developers on our team a little queasy -- is to create a new Set of Stores, then do user.setStores(null), then do user.setStores(stores).
We are using the OpenEntityManagerInViewFilter; we are using Hibernate as our JPA provider; we are using Spring's JpaTransactionManager. We don't have any #Transactional annotations in our code -- and adding them breaks the existing code due to proxy behavior described elsewhere.
Any insight anyone might provide as to how we might solve this problem in a non-queasy manner is welcome.
Relevant part of User.java:
#ManyToMany
#JoinTable(name = "company.user_store_access",
joinColumns = #JoinColumn(name = "userid"),
inverseJoinColumns = #JoinColumn(name = "storeid"))
public Set<Store> getStores() {
return stores;
}
Relevant part of Store.java:
#ManyToMany(mappedBy = "stores")
public List<User> getUsers() {
return users;
}
Relevant parts of UserDetailAction.java:
(pass-throughs down a layer or two, and then:)
entity = getEntityManager().merge(entity);
The problem is that you have a bi-directional relation, and in this case there is one entity that controls the other, and from your mapping the controlling one is the Store entity and not the User, so adding a user to a store would work (since the Store is the one who controls) but adding a store to a user will not.
try inverting the situation, by making the User object the one who controls the relation, by putting the #ManyToMany(mappedBy = "users") annotation on the getStores() method and changing the getUsers() one.
Hope it helped.
P.S. the entity that controls the relation is always the one that doesn't have the mappedBy attribute.
I just got stuck to same problem and found solution on another page.
Here is what has worked for me
calling getJpaTemplate().flush() before calling merge has worked for me.
I did not get the reason why it worked.
Please try the following annotation in your User object:
#ManyToMany(cascade = {CascadeType.ALL})
Related
I downloaded a sample project from here https://www.baeldung.com/spring-data-rest-relationships
I then ran it and did some test REST calls. As far as I can tell you can only update association from the owner side using SDR. What I mean is
public class Book {
#ManyToOne
#JoinColumn(name = "library_id")
private Library library;
}
and
public class Library {
#OneToMany(mappedBy = "library")
private List<Book> books;
}
You can't actually make post/put calls to /libraries/1/books. Server return 204 but no effect on the db whatsoever.
You can however, make post/put calls to /books/1/library and everything works as intended including keeping the other entity in sync.
Is this normal? It's the same behaviour for #ManyToMany as well. Is there a way to allow for updates from both sides? If I write my own API I can certainly make this the case. Why does SDR not do this?
Is this normal?
In a sense, yes. That's exactly how pure JPA would behave when you added a Book to the collection of Library.books with your current mapping - it would make no changes whatsoever.
My guess is that Spring Data Rest doesn't know (or care) which side of the association is the owner side, and just doesn't go the extra mile to make sure updating the inverse side works as well.
Is there a way to allow for updates from both sides?
A workaround could be to simply pretend both sides of the associations were the owning side, i.e.:
public class Library {
#OneToMany
#JoinColumn(name = "library_id")
private List<Book> books;
}
Be advised that this makes Hibernate treat Library.books and Book.library as two completely separate associations. In some corner cases, your entities may not behave the way you would expect. You have been warned.
Updating bidirectional relations from both sides is very tricky and will potentially cause side effects considering Springs endpoint authorization or Spring Data RESTs BeforeLinkSaveEvent and AfterLinkSaveEvent (https://docs.spring.io/spring-data/rest/docs/current/reference/html/#events).
There has to be a strict parent child relation. I don't think that you can configure the desired behaviour.
I have an #Entity that has a field owner:
#Entity
#EntityListeners({TalkListener.class})
public class Talk {
private #ManyToOne(targetEntity = User.class, optional = false) User owner;
// ...
}
Note: I have a listener in place that implementes #PrePersist. From what I have read it is discouraged to lookup request parameters from there (maybe it is also impossible, I didn't go on researching in this direction). There is the section about events in the docs, but it also seems purely entity-related and not taking the context of the request into account.
I would like to infer the owner from the authenticated user that POSTs the request. What is the easiest way to do so? If it is necessary to override the POST a snippet or linked example would be much appreciated.
Update 1:
#NeilMcGuigan suggested using Spring Data JPA's auditing features, which include a #CreatedBy annotation that would clearly solve the original question (and I would accept it as an answer).
Update 2:
A good tutorial about auditing with Spring Data JPA as well as an alternative of getting the principal within entity lifecycle events can be found here.
For educational purpose, what would be another way if I needed to access some value from the request to populate a value in my entity (say my current use case wasn't #CreatedBy but something different)?
I am trying to remove orphan entities when my parent entity no longer referencing the child entities. Here is the my entity sample
#CollectionOfElements(fetch=FetchType.LAZY)
#Cascade(value = org.hibernate.annotations.CascadeType.ALL)
private Set<item> items;
I have also tried org.hibernate.annotations.CascadeType.DELETE_ORPHAN but no success.
But if I change my annotation to #OneToMany(mappedBy="foo", orphanRemoval=true) then it is working fine.
Any idea why it is not working with #CollectionOfElements
#CollectionOfElements is used to map collection of basic types or embeddable objects as described here.
I think this isn't your case, so just use #OneToMany.
Starting to work on a new project... RESTful layer providing services for social network platform.
Neo4j was my obvious choice for main data store, I had the chance to work with Neo before but without exploiting Spring Data abilities to map POJO to node which seems very convenient.
Goals:
The layer should provide support resemble to Facebook Graph API, which defines for each entity/object related properties & connections which can be refer from the URL. FB Graph API
If possible I want to avoid transfer objects which will be serialized to/from domain entities and use my domain pojo's as the JSON's transferred to/from the client.
Examples:
HTTP GET /profile/{id}/?fields=...&connections=... the response will be Profile object contains the requested in the URL.
HTTP GET /profile/{id}/stories/?fields=..&connections=...&page=..&sort=... the response will be list of Story objects according to the requested.
Relevant Versions:
Spring Framework 3.1.2
Spring Data Neo4j 2.1.0.RC3
Spring Data Mongodb 1.1.0.RC1
AspectJ 1.6.12
Jackson 1.8.5
To make it simple we have Profile,Story nodes and Role relationship between them.
public abstract class GraphEntity {
#GraphId
protected Long id;
}
Profile Node
#NodeEntity
#Configurable
public class Profile extends GraphEntity {
// Profile fields
private String firstName;
private String lastName;
// Profile connections
#RelatedTo(type = "FOLLOW", direction = Direction.OUTGOING)
private Set<Profile> followThem;
#RelatedTo(type = "BOOKMARK", direction = Direction.OUTGOING)
private Set<Story> bookmarks;
#Query("START profile=node({self}) match profile-[r:ROLE]->story where r.role = FOUNDER and story.status = PUBLIC")
private Iterable<Story> published;
}
Story Node
#NodeEntity
#Configurable
public class Story extends GraphEntity {
// Story fields
private String title;
private StoryStatusEnum status = StoryStatusEnum.PRIVATE;
// Story connections
#RelatedToVia(type = "ROLE", elementClass = Role.class, direction = Direction.INCOMING)
private Set<Role> roles;
}
Role Relationship
#RelationshipEntity(type = "ROLE")
public class Role extends GraphEntity {
#StartNode
private Profile profile;
#EndNode
private Story story;
private StoryRoleEnum role;
}
At first I didn't use AspectJ support, but I find it very useful for my use-case cause it is generating a divider between the POJO to the actual node therefore I can request easily properties/connections according to the requests and the Domain Driven Design Approach seems very nice.
Question 1 - AspectJ:
Let's say I want to define default fields for an object, these fields will be returned to the client whether if requested in the URL or not...so I have tried #FETCH annotation on these fields but it seems it is not working when using AspectJ.
At the moment I do it that way..
public Profile(Node n) {
setPersistentState(n);
this.id = getId();
this.firstName = getFirstName();
this.lastName = getLastName();
}
Is it the right approach to achieve that? does the #FETCH annotation should be supported even when using AspectJ? I will be happy to get examples/blogs talking about AspectJ + Neo4j didn't find almost anything....
Question 2 - Pagination:
I would like to support pagination when requesting for specific connection for example
/profile/{id}/stories/ , if stories related as below
// inside profile node
#RelatedTo(type = "BOOKMARK", direction = Direction.OUTGOING)
private Set<Story> bookmarks;
/profile/{id}/stories/ ,if stories related as below
// inside profile node
#Query("START profile=node({self}) match profile-[r:ROLE]->story where r.role = FOUNDER and story.status = PUBLIC")
private Iterable<Story> published;
Is pagination is supported out of the box with either #Query || #RelatedTo || #RelatedToVia using Pageable interface to retrieve Page instead of Set/List/Iterable? the limit and the sorting should be dynamic depending on the request from the client... I can achieve that using Cypher Query DSL but prefer to use the basic.. other approaches will be accepted happily.
Question 3 - #Query with {self}:
Kind of silly question but I can't help it :), it seems that when using #Query inside the node entity ( using {self} parameter } the return type must be Iterable which make sense..
lets take the example of...
// inside profile node
#Query("START profile=node({self}) match profile-[r:ROLE]->story where r.role = FOUNDER and story.status = PUBLIC")
private Iterable<Story> published;
When published connection is requested:
// retrieving the context profile
Profile profile = profileRepo.findOne(id);
// getting the publishe stories using AspectJ - will redirect to the backed node
Iterable<Story> published = profile.getPublished();
// set the result into the domain object - will throw exception of read only because the type is Iterable
profile.setPublished(published);
Is there a workaround for that? which is not creating another property which will be #Transiant inside Profile..
Question 4 - Recursive relations:
I am having some problems with transitive / recursive relations, when assigning new Profile Role in Story the relation entity role contain #EndNode story , which contain roles connection...and one of them is the context role above and it is never end :)...
Is there a way to configure the spring data engine not to create these never ending relations?
Question 5 - Transactions:
Maybe I should have mentioned it before but I am using the REST server for the Neo4j DB, from previous reading I understand that there is not support out-of-the-box in transactions? like when using the Embedded server
I have the following code...
Profile newProfile = new Profile();
newProfile.getFollowThem().add(otherProfile);
newProfile.getBookmarks().add(otherStory);
newProfile.persist(); // or profileRepo.save(newProfile)
will this run in transaction when using REST server? there are few operations here, if one fail all fail?
Question 6 - Mongo + Neo4j:
I need to store data which don't have relational nature.. like Feeds, Comments , Massages. I thought about an integration with MongoDB to store these.. can I split domain pojo fields/connections to both mongo/neo4j with cross-store support? will it support AspectJ?
That is it for now.... any comments regarding any approach I presented above will be welcome.. thank you.
Starting to answer, by no means complete:
Perhaps upgrade to the the .RELEASE versions?
Question 1
If you want to serialize AspectJ entities to JSON you have to exclude the internal fields generated by the advanced mapping (see this forum discussion).
When you use the Advanced Mapping #Fetch is not necessary as the data is read-through from the database anyway.
Question 2
For the pagination for fields, you can try to use a cypher-query with #Query and LIMIT 100 SKIP 10 as a fixed parameter. Otherwise you could employ a repository/template to actually fill a Collection in a field of your entity with the paged information.
Question 3
I don't think that the return-type of an #Query has to be an Iterable it should also work with other types (Collections or concrete types). What is the issue you run into?
For creating recursive relationships - try to store the relationship-objects themselves first and only then the node-entities. Or use template.createRelationshipBetween(start, end, type, allowDuplicates) for creating the relationships.
Question 5
As you are using SDN over REST it might not perform very well, as right now the underlying implementation uses the RestGraphDatabase for fine-grained operations and the advanced mapping uses very fine grained calls. Is there any reason why you don't want to use the embedded mode? Against a REST server I would most certainly use the simple-mapping and try to handle read operations mostly with cypher.
With the REST APi there is only one tx per http-call the only option of having larger transactions is to use the rest-batch-api.
There is a pseudo-transaction support in the underlying rest-graph-database which batches calls issued within a "transaction" to be executed in one batch-rest-request. But those calls must not rely on read-results during the tx, those will only be populated after the tx has finished. There were also some issues using this approach with SDN so I disabled it for that (it is a config-option/system-property for the rest-graphdb).
Question 6
Right now cross-store support for both MongoDB and Neo4j is just used against a JPA / relational store. We discussed having cross-store references between the spring-data projects once but didn't follow up on this.
I'm wondering: What is the point of FetchType.LAZY in one(many)-to-many using DAO Pattern?
It is basically useless? As soon as you are outside of your DAO (eg. were the actual work is done) you can't fetch the related data as you are not in a hibernate session anymore.
Lets make an Example:
Student and Class. A student takes many classes. he now logs into the system and his Student entity object is retrieved from the system.
application layer -> Service Layer -> DAO
Now the Student wants to see which classes he takes and oops a LazyInitializationException occurs as we are outside of DAO.
What are the options to prevent this? I've like googled hours and not found a solution for this except to actually fetch everything before leaving the DAO which defeats the purpose of lazy in the first place. (Have read about OpenSessionViewFilter but this should work independent of the application layer)
How do you solve this issue in a good way? What are alternative patterns that don't suffer from this?
EDIT:
I get no LazyInitializationException with following settings:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.compound",
cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(FetchMode.JOIN)
Funny thing is it must be exactly like this.
removing #Fetch -> LazyInitializationException
Even stranger is if I remove orphanRemoval = true, then LazyInitializationException also occurs even with #Fetch. So both of those are required.
Maybe someone could enlighten me why this is the case. Currently I'm tending to ditch hibernate completely as with pure JDBC I would have reached the desired behavior hours ago...
You can always fetch foreign-key relation data without the same ession. Since your session does not exists outside your Application Layer you just fetch it manually in the method where you retrieve the data and set it.
Application Layer:
public List<SchoolClass> getSchoolClassesByStudent(Serializable identifier)
{
List<SchoolClasses> schoolClasses = // get classes by student using criteria or hql
return schoolClasses;
}
Client Layer:
public void loadSchoolClassesByStudent(Student student)
{
student.setSchoolClasses(application.getSchoolClassesByStudent(student.getId()));
}
I myself choosed not to support any collections in my hibernate entities.
I fetch all child relations with very generic methods that my server provides to my client similar to this one.
Edit: Or create some logic (interceptor?) that can check outside the DAO if data is uninitialized before accessing it and initialize it using a generic method.
This would also assume that Hibernate jar's are on the client level, which depends if this is a good idea (Same if the uninitialized data is not set to null).
One way to solve the problem is to use OpenSessionInViewFilter Filter.
<filter>
<filter-name>hibernateSessionFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
</filter>