I have a question concerning Hibernate 3.6.7 and JPA 2.0.
Consider following entities (some getters and setters are omitted for brevity):
@Entity
public class Parent {
@Id
@GeneratedValue
private int id;
@OneToMany(mappedBy="parent")
private List<Child> children = new LinkedList<Child>();
@Override
public boolean equals(Object obj) {
return id == ((Parent)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
@Entity
public class Child {
@Id
@GeneratedValue
private int id;
@ManyToOne
private Parent parent;
public void setParent(Parent parent) {
this.parent = parent;
}
@Override
public boolean equals(Object obj) {
return id == ((Child)obj).id;
}
@Override
public int hashCode() {
return id;
}
}
Now consider this piece of code:
// persist parent entity in a transaction
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Parent parent = new Parent();
em.persist(parent);
int id = parent.getId();
em.getTransaction().commit();
em.close();
// relate and persist child entity in a new transaction
em = emf.createEntityManager();
em.getTransaction().begin();
parent = em.find(Parent.class, id);
// *: parent.getChildren().size();
Child child = new Child();
child.setParent(parent);
parent.getChildren().add(child);
em.persist(child);
System.out.println(parent.getChildren()); // -> [Child@1, Child@1]
em.getTransaction().commit();
em.close();
The child entity is wrongly being inserted twice into the list of children of the parent entity.
When doing one of the following, the code works fine (no duplicate entries in the list):
mappedBy
attribute in the parent entity*
)This is obviously a very weird behavior. Also, when using EclipseLink as the persistence provider, the code works just as expected (no duplicates).
Is this a Hibernate bug or am I missing something?
Thanks
It's a bug in Hibernate. Surprisingly, it's not reported yet, feel free to report it.
Operations against non-initialized lazy collections are queued in order to execute them after collection is initialized, and Hibernate doesn't handle the situation when these operations conflict with the data from the database. Usually it's not a problem, because this queue is cleared upon flush()
, and possible conflicting changes are propagated to the database upon flush()
as well. However, some changes (such as persisting of entities with ids generated by generator of type IDENTITY
, I guess, it's your case) are propagated to the database without the full flush()
, and in these cases conflicts are possible.
As a workaround you can flush()
the session after persisting the child:
em.persist(child);
em.flush();
I fixed this problem by telling Hibernate not to add duplicates in my collection. In your case change the type of your children
field from List<Child>
to Set<Child>
and implement equals(Object obj)
and hashCode()
on the Child
class.
Obviously this will not be possible in every case, but if there is a sane way to identify that a Child
instance is unique then this solution can be relatively painless.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With