[hibernate-issues] [Hibernate-JIRA] Commented: (HHH-3810) Transient entities can be inserted twice on merge

Gail Badner (JIRA) noreply at atlassian.com
Tue Mar 10 20:48:38 EDT 2009


    [ http://opensource.atlassian.com/projects/hibernate/browse/HHH-3810?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=32621#action_32621 ] 

Gail Badner commented on HHH-3810:
----------------------------------

Here is a more complicated test (also based on the test case from HHH-3046) that illustrates the same bug caused by the fix in HHH-3229. 

This test also failed before the fix was applied, but instead it was because a not-null property references a transient value. This failure was originally documented in HHH-3229.

                    -- (N : 1) -- Tour
                    |
                    |  -- (1 : N) -- (pickup) ----
                    |  |                                     |
Route -- (1 : N) -- Node                      Transport
                       |                                     |
                       -- (1 : N) -- (delivery) --

Collections are sets.

Node.route has cascade="none".
All other associations are cascade="merge,refresh".

All many-to-one associations have not-null="true"

There are 5 objects: 
   route (persistent) 
   deliveryNode (transient)
   pickupNode (transient)
   transport (transient)
   tour (transient)

route.nodes { deliveryNode, pickupNode }

tour.nodes = { deliveryNode, pickupNode }

pickupNode.route = route
pickupNode.tour = tour
pickupNode.pickupTransports = { transport }

deliveryNode.route = route
deliveryNode.tour = tour
deliveryNode.deliveryTransports = { transport }

transport.pickupNode = pickupNode
transport.deliveryNode = deliveryNode

The following shows the execution path after applying the fix in HHH-3229, which results in deliveryNode and transport being saved twice. The test ultimately fails with a ConstraintViolationException because there are 2 Transport entities with the same pickupNode.

MERGE EVENT route (persistent, routeID=1 )

  MERGE EVENT deliveryNode
  |  
  | A) CASCADE_BEFORE_SAVE deliveryNode
  |    
  |    MERGE EVENT tour
  |    | 
  |    | A) CASCADE_BEFORE_SAVE tour (nothing to do)
  |    |
  |    | B) SAVE tour (tourID=2)
  |    |
  |    | C) CASCADE_AFTER_SAVE tour
  |    |    
  |    |    MERGE EVENT deliveryNode 
  |    |    | (BUG: embedded merge event for same transient entity!!!)
  |    |    |    
  |    |    | A) CASCADE_BEFORE_SAVE deliveryNode
  |    |    |
  |    |    |    MERGE EVENT tour (skip because it is already saved)
  |    |    |
  |    |    | B) SAVE deliveryNode (nodeID=3)
  |    |    |    (BUG: saved in embedded merge event!!!)
  |    |    |
  |    |    | C) CASCADE_AFTER_SAVE deliveryNode
  |    |    |
  |    |    |    MERGE EVENT transport
  |    |    |    |
  |    |    |    | A) CASCADE_BEFORE_SAVE transport
  |    |    |    |   
  |    |    |    |    MERGE EVENT deliveryNode (skip because it is already saved)
  |    |    |    |   
  |    |    |    |    MERGE EVENT pickupNode
  |    |    |    |    |
  |    |    |    |    | A) CASCADE_BEFORE_SAVE pickupNode
  |    |    |    |    |                
  |    |    |    |    |    MERGE EVENT tour (skip because it is already saved)                
  |    |    |    |    |
  |    |    |    |    | B) SAVE pickupNode (nodeID=4)
  |    |    |    |    |
  |    |    |    |    | C) CASCADE_AFTER_SAVE pickupNode
  |    |    |    |    | 
  |    |    |    |    |    MERGE EVENT transport
  |    |    |    |    |    | (BUG: embedded merge event for same transient entity!!!)
  |    |    |    |    |    | 
  |    |    |    |    |    | A) CASCADE_BEFORE_SAVE transport
  |    |    |    |    |    | 
  |    |    |    |    |    |    MERGE EVENT deliveryNode (skip because it is already saved)
  |    |    |    |    |    |    MERGE EVENT pickupNode (skip because it is already saved)                
  |    |    |    |    |    | 
  |    |    |    |    |    | B) SAVE transport (transportID=5)
  |    |    |    |    |    |    (BUG: saved in embedded merge event!!!)
  |    |    |    |    |    | 
  |    |    |    |    |    | C) CASCADE_AFTER_SAVE transport (nothing to do)
  |    |    |    | 
  |    |    |    | B) SAVE transport (transportID=6)
  |    |    |    |    (BUG: saved again in original merge event!!!)
  |    |    |    |
  |    |    |    | C) CASCADE_AFTER_SAVE transport (nothing to do)
  |    |    |                               
  |    |    MERGE EVENT pickupNode (skip because it is already saved)
  |
  | B) SAVE deliveryNode (nodeID=7)
  |    (BUG: saved again in original merge event!!!)
  |
  | C) CASCADE_AFTER_SAVE deliveryNode
  |
  |    MERGE EVENT transport (skip because it is already saved)

After merge completes, the action queue shows 6 inserts (should only be 4):
   tour [ EntityInsertAction.id=2, tour.tourID=2 ]
   pickupNode [ EntityInsertAction.id=3, pickupNode.nodeID=3 ] 
   deliveryNode [ EntityInsertAction.id=4, deliveryNode.nodeID=7 ] <-- inconsistent ID
   transport [ EntityInsertAction.id=5, transport.transportID=6 ] <-- inconsistent ID
   transport [ EntityInsertAction.id=6, transport.transportID=6 ]
   deliveryNode [ EntityInsertAction.id=7, deliveryNode.nodeID=7 ]

When the session flushes, the test fails with a ConstraintViolationException because there are 2 Transport entities with the same pickupNode

------------------------------------------------------------------------------------------------------------------

The following shows the execution path before applying the fix in HHH-3229, which fails with org.hibernate.PropertyValueException because transport.pickupNode is transient when transport is saved.

MERGE EVENT route (persistent)

  MERGE EVENT deliveryNode
  |  
  | A) CASCADE_BEFORE_SAVE deliveryNode
  |    
  |    MERGE EVENT tour
  |    | 
  |    | A) CASCADE_BEFORE_SAVE tour (nothing to do)
  |    |
  |    | B) SAVE tour
  |    |
  |    | C) CASCADE_AFTER_SAVE tour
  |    |    
  |    |    MERGE EVENT deliveryNode (skip because it is already being merged)
  |    |    
  |    |    MERGE EVENT pickupNode
  |    |    |
  |    |    | A) CASCADE_BEFORE_SAVE pickupNode
  |    |    |                
  |    |    |    MERGE EVENT tour (skip because it is already being merged)                
  |    |    |
  |    |    | B) SAVE pickupNode
  |    |    |
  |    |    | C) CASCADE_AFTER_SAVE pickupNode
  |    |    | 
  |    |    |    MERGE EVENT transport
  |    |    |
  |    |    |    | A) CASCADE_BEFORE_SAVE transport
  |    |    |    |   
  |    |    |    |    MERGE EVENT deliveryNode (skip because it is already being merged)
  |    |    |    |
  |    |    |    |    MERGE EVENT pickupNode (skip because it is already being merged)                
  |    |    |    |
  |    |    |    | B) SAVE transport 
  |    |    |    |    (fails because transport.deliveryNode is non-nullable, but is still transient!!!) 



> Transient entities can be inserted twice on merge
> -------------------------------------------------
>
>                 Key: HHH-3810
>                 URL: http://opensource.atlassian.com/projects/hibernate/browse/HHH-3810
>             Project: Hibernate Core
>          Issue Type: Bug
>          Components: core
>    Affects Versions: 3.2.6, 3.3.0.GA, 3.3.1
>            Reporter: Gail Badner
>            Assignee: Gail Badner
>             Fix For: 3.2.x, 3.3.x, 3.5
>
>
> A transient entity being merged can be inserted twice if there is a cascade back to that same entity before it has been saved.
> This bug was introduced by the fix for HHH-3229.
> This can be illustrated by the following mapping (adapted from the test case at HHH-3046):
> Route -- (1 : N) -- Node -- (N : 1) -- Tour            
> There are 3 objects:
>    route (persistent) 
>    pickupNode (transient)
>    tour (transient)
> Collections are sets.
> node.route has cascade="none".
> All other associations are cascade="merge,refresh"
> route.nodes { pickupNode }
> tour.nodes = { pickupNode }
> pickupNode.route = route
> pickupNode.tour = tour
> The following shows the execution path that results in pickupNode being saved twice.
> MERGE EVENT route (persistent)
>   MERGE EVENT pickupNode (transient)
>   |  
>   | A) CASCADE_BEFORE_SAVE pickupNode
>   |    
>   |    MERGE EVENT tour (transient)
>   |    | 
>   |    | A) CASCADE_BEFORE_SAVE tour (nothing to do)
>   |    |
>   |    | B) SAVE tour
>   |    |
>   |    | C) CASCADE_AFTER_SAVE tour
>   |    |    
>   |    |    MERGE EVENT pickupNode (still transient)
>   |    |    | (BUG: embedded merge event for same transient entity!!!)
>   |    |    |    
>   |    |    | A) CASCADE_BEFORE_SAVE pickupNode
>   |    |    |
>   |    |    |    MERGE EVENT tour (skip because it is already merged)
>   |    |    |
>   |    |    | B) SAVE pickupNode 
>   |    |    |    (BUG: saved in embedded merge event!!!)
>   |    |    |
>   |    |    | C) CASCADE_AFTER_SAVE pickupNode
>   |    |    |
>   |
>   | B) SAVE pickupNode
>   |    (BUG: saved again in original merge event!!!)
>   |
>   | C) CASCADE_AFTER_SAVE pickupNode (nothing to do)
>   
> Prior to applying the fix for HHH-3229, the execution path was:
> MERGE EVENT route (persistent)
>   MERGE EVENT pickupNode (transient)
>   |  
>   | A) CASCADE_BEFORE_SAVE pickupNode
>   |    
>   |    MERGE EVENT tour (transient)
>   |    | 
>   |    | A) CASCADE_BEFORE_SAVE tour (nothing to do)
>   |    |
>   |    | B) SAVE tour
>   |    |
>   |    | C) CASCADE_AFTER_SAVE tour
>   |    |    
>   |    |    MERGE EVENT pickupNode (still transient; skip because it is already being merged)
>   |  
>   | B) SAVE pickupNode
>   |
>   | C) CASCADE_AFTER_SAVE pickupNode (nothing to do)

-- 
This message is automatically generated by JIRA.
-
If you think it was sent incorrectly contact one of the administrators: http://opensource.atlassian.com/projects/hibernate/secure/Administrators.jspa
-
For more information on JIRA, see: http://www.atlassian.com/software/jira

        



More information about the hibernate-issues mailing list