I am using JPA and Eclipselink.
My problem is that in a @ManyToMany bidirectional relationship how to add a new object on the owner side correctly when the other side object was created and persisted earlier.
I have two entity classes, Group and Person and there is a @ManyToMany bidirectional relation between them. Group is the owner side so the mappedBy attribute is declared in the Person class:
@Entity
public class Group {
[...]
@ManyToMany // no mappedBy -> this is the owner side
List<Person> persons;
}
@Entity
public class Person {
[...]
@ManyToMany(mappedBy="persons")
List<Group> groups;
}
When I create Group objects first and later add Persons, everything works fine. But if first I create and persist Persons and later add Groups, I have some problem. Here's the code:
[...]
// Creating and persisting some Person object
em.persist(new Person("Sneezy")); // em is the EntityManager
em.persist(new Person("Happy"));
[...]
// (the two person objects are retreived from the database)
List<Person> personList = findAllPerson();
// Creating a new Group object
Group g = new Group("Dwarfs");
// Adding the Person objects to the new Group object
g.getPersons().add(personList.get(0));
g.getPersons().add(personList.get(1));
// Refreshing the other direction too:
personList.get(0).getGroups().add(g);
personList.get(1).getGroups().add(g);
// Persisting the new Group object
em.persist(g);
Now I can check that the database contains all data correctly. The new Group is in the group table and the proper relation is in the group_person join table.
However if I make a query for the Group objects, it contains the related Person objects, but that Person objects' list of group don't contain the Group object to which they were added. So the back direction is not there.
List<Group> groups = findAllGroup();
List<Person> persons = findAllPerson();
Why not? The database contains all the data for it. And really if I make a JPA cache clearing before the retreival, the back direction is there. So my problem seems to be a caching problem in JPA. My cache clearing code:
em.getEntityManagerFactory().getCache().evictAll();
Ok, let's think. I see that the modification in the person class was not actually merged (only the Group object was persisted), so the JPA cache contains Person objects without the back relation. Ok, lets try the cascading to solve this problem. I have added CascadyType.PERSIST to the Groups class persons property.
@ManyToMany(cascade=CascadeType.PERSIST)
List<Person> persons;
But in this case the persisting of the new group object creates new instance of person objects in the database instead of using the existing person objects that was retreived and added to the group object.
Ok, let's try CascadeType.MERGE:
@ManyToMany(cascade=CascadeType.MERGE)
List<Person> persons;
In this case the persisting operation on the new Group object makes correct data in the backing database, however the JPA cache still contains the Person objects with the lacking direction.
Next idea is that after persisting the Group object I make a merge operation too, to induce a cascading merge in the Person objects.
em.persist(g);
em.merge(g);
And voila, the merge operation (together with the CascadeType.MERGE parameter in the Group class) solved the problem.
But I don't like either the cache clearing or this solution. I think clearing the cache is only necessary when the database is changed in the background by other threads. But I changed the database through the same EntityManager so it should know all about the changes without clearing its cache. And the other solution is also not OK. It is not logical for me why I should make a merge operation right after a persist operation.
I think there should be a common real solution for this problem. Anybody can help?