Hi all,
I'm having a problem with hibernate's implementation of the EntityManager interface - specifically the contains() method, and I'd appreciate any help you can offer.
My application works with a complex graph of objects, and implements the session per request pattern, using detached objects while the graph is constructed and merging when it comes to persist. This application works exclusively with the JPA interfaces, hiding the hibernate implementation.
My problem is specifically related to the graph of objects and cascading merges. The following example is a drastic oversimplification (and please excuse the pseudocode!) but illustrates my problem:
Code:
@MappedSuperClass
public abstract Class Person {
// acc is a business key for every person instance
@Column
private String acc;
...
}
@Entity
public class Parent extends Person {
@OneToMany{
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH})
...}
private Child child;
public void setChild(Child child) {...}
public Child getChild() {...}
}
@Entity
public class Child extends Person {
...
}
Assume that my application contains references to a set of parent instances and a set of child instances. Some of the child instances are references by a parent object, some aren't. My application needs to persist all of them. Effectively, my application is doing the following (but obviously with a much more complex graph of objects, and person and child objects will be constructed in separate classes in a multithreaded manner)...
Code:
Person parent = new Parent();
Person child = new Child();
parent.setChild(child);
// then sometime later...
entityManager.merge(parent);
entityManager.merge(child);
// then, finally...
transaction.commit();
This fails: because parent cascades the merge to child, my child instance is then stale and when I try to commit I get a ConstraintViolationException (because I'm trying to store two child entires with the same acc). Ideally, I'd simply update my applications references to child after merging but I can't do this for objects that are merged due to a cascade from parent. Further, I don't know in advance whether a reference to child has been set on parent: due to the nature of my set of objects to persist, some may be reached transitively, others not.
I'd hoped that I can simply iterate over a list of instances stored in my app, with the following code...
Code:
for (Person person : getAll()) {
if (!entityManager.contains(person)) {
entityManager.merge(person);
}
}
But, this doesn't work: contains(person) is always false. Even though I have overridden equals() and hashcode() methods as appropriate, I don't get the result I'd expect if contains() followed normal hashset semantics. So I can write code lilke this...
Code:
Person mergedPerson = entityManager.merge(person);
log.info("Original person equals merged person? " + person.equals(mergedPerson)); // this is true
log.info("EntityManager contains original person? " + entityManager.contains(person)); // this is false
log.info("EntityManager contains merged person? " + entityManager.contains(mergedPerson)); // this is true
In this case, because person and mergedPerson are equal, I would have expected the second call to return true, not false. Given that my graph of objects is complex and it is not easy to keep track of, and update, all the references between all objects, is there some other way I can check whether a person has already been merged?
Thanks in advance,
Tony.