hibernate/hibernate-core — pull request #240 by gbadner (2012-01-03 12:50:19+00:00)
Title: HHH-5472 : Delay saving an entity if it does not cascade the save to non-nullable transient entities

The proposed fix has no affect on the cascade algorithm itself. IOW, EntityInsertAction and EntityIdentityInsertAction objects are added to the ActionQueue in the same order as before.

Some new classes have been introduced:

AbstractEntityInsertAction:
Both EntityInsertAction and EntityIdentityInsertAction extend AbstractEntityInsertAction.

NonNullableTransientDependencies:
Tracks non-nullable transient entities that would cause a particular entity insert to fail

UnresolvedEntityInsertActions:
Tracks entity insert actions that have non-nullable transient entities that would cause the entity insert to fail.

1) When an EntityInsertAction or EntityIdentityInsertAction object is added to the ActonQueue, AbstractEntityInsertAction.findNonNullableTransientEntities() is called to find any transient entities that would cause the insert to fail;

2) If the entity insert action would fail due to non-nullable transient entities, then that action is added to unresolvedInsertions (in ActionQueue).

3) Otherwise, execute ActionQueue.addResolvedEntityInsertAction( insert ) which:
a) the action is processed (i.e., added to ActionQueue.insertions or executed)
b) the entity status is changed to Status.MANAGED
c) unresolvedInsertions.resolveDependentActions( entity, session ) resolves dependencies
on the entity and returns a Set of actions that no longer have any dependencies
d) for each resolved action in the Set, execute 3).

4) If there ActionQueue still has unresolved entity insert actions when the operaton completes, then TransientObjectException is thrown.

This model (used by org.hibernate.test.annotations.cascade.multicircle tests) illustrates the proposed fix:

------------------------------ N G
|
| 1
| |
| |
| N
|
| E N--------------0,1 * F
|
| 1 N
| | |
| | |
1 N |
* |
B * N---1 D * 1------------------
*
N N
| |
| |
1 |
|
C * 1-----

In the diagram:
- all associations are bidirectional.
- assocations marked with '*' cascade persist, save, merge operations to the associated entities
(e.g., B cascades persist to D, but D does not cascade persist to B)

If b, c, d, e, f, and g are all transient unsaved that are associated with each other, then when Session.persist( b ) is called, then entities are added to the ActionQueue in the following order:

c, d (depends on e), f (depends on d, g), e, b, g

Before the fix, entities would be inserted in the same order they were added to the ActionQueue, resulting in an exception when saving d (because d.e is non-nullable and transient)

Using the proposed fix, entities will be successfully inserted in the following order:

c, e, d, b, g, f

In this case, inserting d is delayed until after e is inserted, and inserting f is delayed until after e and g are inserted.