23

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

  • remove the mappedBy attribute in the parent entity
  • perform some read operation on the list of children (e.g. uncomment line marked by *)

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

jeha
  • 10,562
  • 5
  • 50
  • 69
user1014562
  • 233
  • 1
  • 2
  • 5
  • Could you add the code of the setParent method and of the equals/hashCode methods? – JB Nizet Oct 26 '11 at 14:55
  • I just added the methods you asked for. However, I don't think this issue is related to equals / hashCode. – user1014562 Oct 26 '11 at 15:10
  • 2
    Your equals methods don't respect the contract of Object.equals. Moreover, the hashCode changes when the ID is generated and assigned to the entity. I wouldn't be surprised if the bug disappeared once you remove hashCode and equals. BTW. Hibernate recommends not to use ID to implement equals and hashCode. – JB Nizet Oct 26 '11 at 15:38
  • Thanks for your comment. I wasn't aware of the controversy of using the persistent id within equals and hashCode. But even if I remove my equals and hashCode implementations, the problem remains the same. – user1014562 Oct 27 '11 at 07:31

5 Answers5

31

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();
mixel
  • 25,177
  • 13
  • 126
  • 165
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • 1
    Thanks for your answer and the workaround! I have created a bug report and referred to your answer: [HHH-6776](https://hibernate.onjira.com/browse/HHH-6776) – user1014562 Oct 27 '11 at 08:28
  • It is really strange that this bug is not solved yet? It is so poor to need to read the objects before persisting. – nize Dec 13 '13 at 15:58
  • 1
    I also have duplicate in oneToMany with a list. Changing to set does help, however, why I am forced to use set when I want to use list? Using the generated sql query of hibernate returns NO duplicats. However, hibernate returns duplicats in the list even the query has no duplicates returned. Cannot find solution whithout using set. I do not need to use flush, because of stateless-bean.. – nimo23 Jan 23 '18 at 14:11
  • This bug is now fixed in Hibernate 5.0.8: https://hibernate.atlassian.net/browse/HHH-5855 – cst1992 Feb 10 '18 at 10:06
  • 2
    I am using hibernate 5.1.3 and issue is still reproducible – Pranit Jun 13 '20 at 13:48
4

I came across this question when I had issues not with adding items to a list annotated with @OneToMany, but when trying to iterate over the items of such a list. The items in the list were always duplicated, sometimes a lot more than twice. (Also happened when annotated with @ManyToMany). Using a Set was not a solution here, since these lists were supposed to allow duplicate elements in them.

Example:

@OneToMany(mappedBy = "parent", fetch = FetchType.EAGER)
@Cascade(CascadeType.ALL)
@LazyCollection(LazyCollectionOption.FALSE)
private List entities;

As it turned out, Hibernate executes sql statements using left outer join, which can result in duplicate results returned by the db. What helped was simply defining an ordering on the result, using OrderColumn:

@OrderColumn(name = "columnName")
xor_eq
  • 3,933
  • 1
  • 29
  • 33
  • can't thank you enough, can you point me to a resource where i can read more about this ? – Sujal Mandal Feb 26 '17 at 07:15
  • When annotated with `@OrderColumn`, Hibernate will use `PersistentOrderedSet`, which honors `equals()` and `hashCode()` of the objects. Bug ticket: [PersistentSet does not honor hashcode/equals contract when loaded eagerly ](https://hibernate.atlassian.net/browse/HHH-3799). Source: https://stackoverflow.com/a/49355892/1125678 – kntx Nov 27 '20 at 10:01
3

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.

carbontax
  • 2,164
  • 23
  • 37
0

By using Java enterprise context in Wildfly (8.2.0-Final) (I think it's Hibernate version 4.3.7) the workaround for me was, to first persist the child an the add it to the lazy collection:

...
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void test(){
    Child child = new Child();
    child.setParent(parent);
    childFacade.create(child);

    parent.getChildren().add(cild);

    parentFacade.edit(parent);
}
McIntosh
  • 2,051
  • 1
  • 22
  • 34
-2

Managed this by just calling the empty() method. For this scenario,

parent.getChildren().isEmpty()

before

parent.getChildren().add(child);
lakshitha
  • 77
  • 2
  • 9