Multi-Level cascading of unsaved instances with bidirectional associations fails with TransientObjectException (Carl-Eric Menzel)

Description

Given the following classes (pseudocode, full source in the attached zipfile):

Top {
@OneToMany(cascade = { CascadeType.ALL }, mappedBy = "top")
List<Middle> middles
}

Middle {
@ManyToOne
Top top

@OneToOne(cascade = { CascadeType.ALL })
@JoinColumn(name = "BOTTOM_ID")
Bottom bottom
}

Bottom {
@OneToOne(mappedBy = "bottom")
Middle middle
}

The following code fails on the second flush with a TransientObjectException:

Top top = new Top();
em.persist(top);
em.flush();
// top is now attached, status MANAGED
Middle middle = new Middle(1l);
middle.setBottom(new Bottom());
top.addMiddle(middle);
Middle middle2 = new Middle(2l);
middle2.setBottom(new Bottom());
top.addMiddle(middle2);
// both middles and bottoms are transient but should be saved by cascade
em.flush(); // boom!

The relevant part of the error is:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: test.Bottom.middle -> test.Middle
at org.hibernate.engine.CascadingAction$9.noCascade(CascadingAction.java:376)
at org.hibernate.engine.Cascade.cascade(Cascade.java:163)

The same code works without exception when using EclipseLink instead of Hibernate EntityManager, so I am assuming that the test code is valid.

Some debugging revealed that CascadingAction.PERSIST_ON_FLUSH.noCascade() checks the inverse association from Bottom to Middle. It then calls a private method isInManagedState to verify that this association does not reference an unsaved instance. It does this by checking the entities EntityEntry.getStatus() value, which it expects to be MANAGED or READ_ONLY. At this time, however, the Middle instance is in the middle (no pun intended) of its own cascading save cycle and therefore its status is SAVING. This, to me, seems to be a valid session-attached status, so isInManagedState() should also accept this state.

The attached patch against Hibernate Core 3.3.2 implements just that, and this change makes the above example code work as expected. The Hibernate test suite did not complain, so I am reasonably sure I did not break anything else.

See also the attached cascadetest.zip for a ready-to-use testcase that demonstrates the error with Hibernate Core 3.3.2. Change the dependency in the supplied pom.xml to a patched version of Hibernate Core, and the test will no longer fail.

Carl-Eric Menzel
C1 SetCon GmbH

Attachments

2
  • 09 Jun 2010, 12:09 PM
  • 09 Jun 2010, 12:09 PM

Activity

Zbyněk RoubalíkFebruary 28, 2012 at 4:06 PM

Please ignore previous comment, this problem was already fixed.

Zbyněk RoubalíkFebruary 28, 2012 at 3:06 PM

Test for this issue is failing on MS SQL Server 2008, because the test uses reserved keyword 'TOP' as a name for a table.

Strong LiuFebruary 8, 2012 at 5:37 PM

Former userJanuary 27, 2012 at 9:44 AM

Fixed in master and 3.6 branch.

Thanks for the test and fix!

Gail

Former userJanuary 26, 2012 at 3:53 AM

The attached patch is a partial fix because it only takes into account that the transient entity is already in the process of being saved.

It does not cover the case where the transient entity is not yet in the process of being saved, but it would be saved by a different cascade path that has not yet been followed. I will cover this scenario in HHH-6999.

Fixed

Details

Assignee

Reporter

Fix versions

Affects versions

Priority

Created June 9, 2010 at 12:09 PM
Updated February 28, 2012 at 4:06 PM
Resolved January 27, 2012 at 9:44 AM

Flag notifications