1

I'm seeing behavior in Hibernate (3.6.10) that doesn't match my mental model of what should be happening, and I'd like to know if this means there's a bug, there was a conscious decision made to break from the (otherwise-valid) mental model, or my mental model is wrong.

I have a bi-directional OneToMany/ManyToOne relationship between Parent and Child, where Parent has many Children and Child is the owner of the relationship:

@Entity
public class Child {
    @ManyToOne
    @JoinColumn(name="PARENTID", nullable=false)
    private Parent parent;
}

@Entity
public class Parent {
    @OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
    private List<Child> children;
}

And my test (in an @Transactional JUnit test with the SpringJunit4ClassRunner):

@Test
public void test() {
    Child child = new Child();
    child.setName("TEST");
    childDao.saveOrUpdate(child);
    childDao.getSession.flush();
    childDao.getSession.evict(child);

    Parent parent = new Parent();
    parent.setChild(child);
    child.addParent(parent);

    childDao.merge(child);
}

My mental model says that changes to the relationship between Parent and Child get persisted by modifying the Child's Parent entity reference and saving the Child; changes to the Child's children collection are ignored by Hibernate.

Yet if I create new (transient) Child objects and add them to a detached Parent object, when I call sessionFactory.merge(parent), the merge() call appears to inspect the Parent's children collection and reject the merge because those children are transient, even though my code will be iterating through the children as soon as the merge() call returns. (I get the same behavior with a new Parent object whose natural key matches one already in the database.)

Java Persistence With Hibernate has a single bullet that relates to this question (from the bottom of p. 413 of the 2007 corrected fifth edition): "Merging includes all value-typed properties and all additions and removals of elements to any collection." If the statement was "to any non-inverse collection", that would match my mental model (i.e. persist all changes that would be persisted by saveOrUpdate()), but the inclusion of inverse collections in the word "any" conflicts with my mental model.

Can anyone explain why merge() is considering the state of objects in an inverse Collection (one using mappedBy), even though saveOrUpdate() will ignore them? And is there any setting that would instruct merge() to ignore those entities?

NOTE: This question is not the same as JPA2 and hibernate - why does merge store child entities whilst persist does not? (though the titles sound exactly the same), because that author is trying to cascade (and having it not happen with persist()) whereas I do not want automatic cascading behavior.

Community
  • 1
  • 1
Tim
  • 2,027
  • 15
  • 24

1 Answers1

1

The merge operation copies the incoming entity state to a either an attached entity or a freshly loaded entity snapshot.

I added a test on GitHub to replicate your saying with Hibernate 4.3.8 and it works just fine. The Child entity is ignored on the inverse side, and a transient entity is simply ignored.

Both transient entities:

doInTransaction(session -> {
    Post _post = new Post("Post");
    _post.getComments().add(new Comment());
    session.persist(_post);
    return _post;
});

and detached ones:

final Post post = doInTransaction(session -> {
    Post _post = new Post("Post");
    session.persist(_post);
    return _post;
});

doInTransaction(session -> {
    post.getComments().add(new Comment());
    session.merge(post);
});

are properly saved and the inverse side is ignored.

Because this fails under 3.6.10 and works as expected under 4.3.8, it seems highly likely that this is simply a bug in Hibernate 3.6.10 that is fixed in a version somewhere between 3.6.10 and 4.3.8.

Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
  • That makes perfect sense -- for non-inverse one-to-many collections. For inverse ones, the contents of those collections are (as I understand it) **not** part of the entity's state. Changes made to that collection on a persistent entity are not persisted and have no effect on the database, since changes to that relationship are managed solely through the many-to-one entity in Child. Given that, why does Hibernate have to copy any state, and why would that state persistence not be done (only) when I merge the Child objects, just as would happen if my object was persistent and attached? – Tim Mar 26 '15 at 22:58
  • Or, to attack the question from another angle: can you please describe how persistence works for relationships in one of these bi-directional one-to-many/many-to-one relationships, where the description is consistent and accounts for both the fact that modifications to a persistent entity ignore changes made in the inverse collection and the fact that modifications to that collection are considered during `merge()`? If my mental model is too simplistic, I'd love to have a better one so I understand what Hibernate is doing on my behalf. – Tim Mar 26 '15 at 23:02
  • I tried to replicate your issue and I couldn't. Check my updated answer. – Vlad Mihalcea Mar 27 '15 at 06:49
  • One difference I see between your test and my code is that you're re-using your Post object whereas I'm merging a completely new object that's never been associated with a Hibernate session (I'll update my post to show this). I'm not sure it'll make a difference, but would you mind giving it a shot to see if you get the same behavior I do? Also, can you confirm that you're using 3.6.10_Final for your test? – Tim Mar 27 '15 at 16:42
  • In your question you said: "add them to a detached Parent object", that's why I used a detached object too. I am using 4.3.8, so if it had been a bug it got fixed. – Vlad Mihalcea Mar 27 '15 at 17:03
  • You're right, my description wasn't as precise as it should have been; sorry for that confusion. I've confirmed that I see the same behavior (rejection due to the transient Child entity) either way. Would you mind modifying your test quickly to check whether creating a new Post changes the behavior you're seeing under 4.3.8? If it doesn't, then I agree that it's a bug that got fixed along the way from 3.6.10 to 4.3.8 and we'll look at upgrading... – Tim Mar 27 '15 at 17:10
  • I am not at work, so I can't change it know. You can fork the repo or download it and simply run that test. You only need java 1.8 and Maven. – Vlad Mihalcea Mar 27 '15 at 17:30
  • I updated my answer. Don't forget to upvote it and accept the answer, if that's what you were looking for. – Vlad Mihalcea Mar 27 '15 at 21:30
  • I edited your answer to include the actual answer that we came to in the comments: that this is a bug in 3.6.10 that is not present in 4.3.8 which we presume means it was fixed somewhere between the two. That way readers don't have to dig through the comments for that information, and don't get the impression that this isn't a bug under 3.6.10. Once that edit clears peer review, I'll accept the answer (I hadn't forgotten, just wanted to test that it actually is the answer before doing so). Thanks for your help figuring out that this isn't an issue in later versions of Hibernate. – Tim Mar 27 '15 at 23:22
  • You are welcome. Migrating to 4.3 is the way to go, since the 3.6 is quite an old version and might not any bug fixes any time soon. – Vlad Mihalcea Mar 27 '15 at 23:25