2525import org .hibernate .engine .spi .PersistenceContext ;
2626import org .hibernate .engine .spi .PersistentAttributeInterceptor ;
2727import org .hibernate .engine .spi .SelfDirtinessTracker ;
28+ import org .hibernate .engine .spi .SessionFactoryImplementor ;
2829import org .hibernate .engine .spi .SessionImplementor ;
2930import org .hibernate .event .spi .EntityCopyObserver ;
3031import org .hibernate .event .spi .EventSource ;
4041import org .hibernate .proxy .LazyInitializer ;
4142import org .hibernate .stat .spi .StatisticsImplementor ;
4243import org .hibernate .type .CollectionType ;
44+ import org .hibernate .type .CompositeType ;
4345import org .hibernate .type .EntityType ;
4446import org .hibernate .type .ForeignKeyDirection ;
47+ import org .hibernate .type .Type ;
4548import org .hibernate .type .TypeHelper ;
4649
4750import static org .hibernate .engine .internal .ManagedTypeHelper .asPersistentAttributeInterceptable ;
4851import static org .hibernate .engine .internal .ManagedTypeHelper .asSelfDirtinessTracker ;
4952import static org .hibernate .engine .internal .ManagedTypeHelper .isHibernateProxy ;
5053import static org .hibernate .engine .internal .ManagedTypeHelper .isPersistentAttributeInterceptable ;
5154import static org .hibernate .engine .internal .ManagedTypeHelper .isSelfDirtinessTracker ;
55+ import static org .hibernate .event .internal .EntityState .getEntityState ;
5256
5357/**
5458 * Defines the default copy event listener used by hibernate for copying entities
@@ -144,15 +148,70 @@ private void doMerge(MergeEvent event, MergeContext copiedAlready, Object entity
144148 }
145149
146150 private void merge (MergeEvent event , MergeContext copiedAlready , Object entity ) {
147- switch ( entityState ( event , entity ) ) {
151+ final EventSource source = event .getSession ();
152+ // Check the persistence context for an entry relating to this
153+ // entity to be merged...
154+ final PersistenceContext persistenceContext = source .getPersistenceContextInternal ();
155+ EntityEntry entry = persistenceContext .getEntry ( entity );
156+ final EntityState entityState ;
157+ final Object copiedId ;
158+ final Object originalId ;
159+ if ( entry == null ) {
160+ final EntityPersister persister = source .getEntityPersister ( event .getEntityName (), entity );
161+ originalId = persister .getIdentifier ( entity , source );
162+ if ( originalId != null ) {
163+ final EntityKey entityKey ;
164+ if ( persister .getIdentifierType ().isComponentType () ) {
165+ /*
166+ this is needed in case of composite id containing an association with a generated identifier, in such a case
167+ generating the EntityKey will cause a NPE when trying to get the hashcode of the null id
168+ */
169+ copiedId = copyCompositeTypeId (
170+ originalId ,
171+ (CompositeType ) persister .getIdentifierType (),
172+ source ,
173+ copiedAlready
174+ );
175+ entityKey = source .generateEntityKey ( copiedId , persister );
176+ }
177+ else {
178+ copiedId = null ;
179+ entityKey = source .generateEntityKey ( originalId , persister );
180+ }
181+ final Object managedEntity = persistenceContext .getEntity ( entityKey );
182+ entry = persistenceContext .getEntry ( managedEntity );
183+ if ( entry != null ) {
184+ // we have a special case of a detached entity from the
185+ // perspective of the merge operation. Specifically, we have
186+ // an incoming entity instance which has a corresponding
187+ // entry in the current persistence context, but registered
188+ // under a different entity instance
189+ entityState = EntityState .DETACHED ;
190+ }
191+ else {
192+ entityState = getEntityState ( entity , event .getEntityName (), entry , source , false );
193+ }
194+ }
195+ else {
196+ copiedId = null ;
197+ entityState = getEntityState ( entity , event .getEntityName (), entry , source , false );
198+ }
199+ }
200+ else {
201+ copiedId = null ;
202+ originalId = null ;
203+ entityState = getEntityState ( entity , event .getEntityName (), entry , source , false );
204+ }
205+
206+ switch ( entityState ) {
148207 case DETACHED :
149- entityIsDetached (event , copiedAlready );
208+ entityIsDetached ( event , copiedId , originalId , copiedAlready );
150209 break ;
151210 case TRANSIENT :
152- entityIsTransient (event , copiedAlready );
211+ entityIsTransient ( event , copiedId != null ? copiedId : originalId , copiedAlready );
153212 break ;
154213 case PERSISTENT :
155- entityIsPersistent (event , copiedAlready );
214+ entityIsPersistent ( event , copiedAlready );
156215 break ;
157216 default : //DELETED
158217 if ( event .getSession ().getPersistenceContext ().getEntry ( entity ) == null ) {
@@ -165,7 +224,7 @@ private void merge(MergeEvent event, MergeContext copiedAlready, Object entity)
165224 )
166225 );
167226 event .getSession ().getActionQueue ().unScheduleUnloadedDeletion ( entity );
168- entityIsDetached (event , copiedAlready );
227+ entityIsDetached (event , copiedId , originalId , copiedAlready );
169228 break ;
170229 }
171230 throw new ObjectDeletedException (
@@ -176,30 +235,37 @@ private void merge(MergeEvent event, MergeContext copiedAlready, Object entity)
176235 }
177236 }
178237
179- private static EntityState entityState ( MergeEvent event , Object entity ) {
180- final EventSource source = event . getSession ();
181- // Check the persistence context for an entry relating to this
182- // entity to be merged...
183- final PersistenceContext persistenceContext = source . getPersistenceContextInternal ();
184- EntityEntry entry = persistenceContext . getEntry ( entity );
185- if ( entry == null ) {
186- EntityPersister persister = source . getEntityPersister ( event . getEntityName (), entity );
187- Object id = persister . getIdentifier ( entity , source );
188- if ( id != null ) {
189- final EntityKey entityKey = source . generateEntityKey ( id , persister );
190- final Object managedEntity = persistenceContext . getEntity ( entityKey ) ;
191- entry = persistenceContext . getEntry ( managedEntity );
192- if ( entry != null ) {
193- // we have a special case of a detached entity from the
194- // perspective of the merge operation. Specifically, we have
195- // an incoming entity instance which has a corresponding
196- // entry in the current persistence context, but registered
197- // under a different entity instance
198- return EntityState . DETACHED ;
238+ private static Object copyCompositeTypeId (
239+ Object id ,
240+ CompositeType compositeType ,
241+ EventSource session ,
242+ MergeContext mergeContext ) {
243+ final SessionFactoryImplementor sessionFactory = session . getSessionFactory ( );
244+ final Object idCopy = compositeType . deepCopy ( id , sessionFactory );
245+ final Type [] subtypes = compositeType . getSubtypes ( );
246+ final Object [] propertyValues = compositeType . getPropertyValues ( id );
247+ final Object [] copyValues = compositeType . getPropertyValues ( idCopy );
248+ for ( int i = 0 ; i < subtypes . length ; i ++ ) {
249+ final Type subtype = subtypes [ i ] ;
250+ if ( subtype . isEntityType () ) {
251+ // the value of the copy in the MergeContext has the id assigned
252+ final Object o = mergeContext . get ( propertyValues [ i ] );
253+ if ( o != null ) {
254+ copyValues [ i ] = o ;
255+ }
256+ else {
257+ copyValues [ i ] = subtype . deepCopy ( propertyValues [ i ], sessionFactory ) ;
199258 }
200259 }
260+ else if ( subtype .isComponentType () ) {
261+ copyValues [i ] = copyCompositeTypeId ( propertyValues [i ], (CompositeType ) subtype , session , mergeContext );
262+ }
263+ else {
264+ copyValues [i ] = subtype .deepCopy ( propertyValues [i ], sessionFactory );
265+ }
201266 }
202- return EntityState .getEntityState ( entity , event .getEntityName (), entry , source , false );
267+ compositeType .setPropertyValues ( idCopy , copyValues );
268+ return idCopy ;
203269 }
204270
205271 protected void entityIsPersistent (MergeEvent event , MergeContext copyCache ) {
@@ -214,14 +280,13 @@ protected void entityIsPersistent(MergeEvent event, MergeContext copyCache) {
214280 event .setResult ( entity );
215281 }
216282
217- protected void entityIsTransient (MergeEvent event , MergeContext copyCache ) {
283+ protected void entityIsTransient (MergeEvent event , Object id , MergeContext copyCache ) {
218284 LOG .trace ( "Merging transient instance" );
219285
220286 final Object entity = event .getEntity ();
221287 final EventSource session = event .getSession ();
222288 final String entityName = event .getEntityName ();
223289 final EntityPersister persister = session .getEntityPersister ( entityName , entity );
224- final Object id = persister .getIdentifier ( entity , session );
225290 final Object copy = copyEntity ( copyCache , entity , session , persister , id );
226291
227292 // cascade first, so that all unsaved objects get their
@@ -231,7 +296,6 @@ protected void entityIsTransient(MergeEvent event, MergeContext copyCache) {
231296 copyValues ( persister , entity , copy , session , copyCache , ForeignKeyDirection .FROM_PARENT );
232297
233298 saveTransientEntity ( copy , entityName , event .getRequestedId (), session , copyCache );
234- persister .setIdentifier ( entity , persister .getIdentifier ( copy , session ), session );
235299
236300 // cascade first, so that all unsaved objects get their
237301 // copy created before we actually copy
@@ -318,17 +382,25 @@ private void saveTransientEntity(
318382 }
319383 }
320384
321- protected void entityIsDetached (MergeEvent event , MergeContext copyCache ) {
385+ protected void entityIsDetached (MergeEvent event , Object copiedId , Object originalId , MergeContext copyCache ) {
322386 LOG .trace ( "Merging detached instance" );
323387
324388 final Object entity = event .getEntity ();
325389 final EventSource source = event .getSession ();
326390 final EntityPersister persister = source .getEntityPersister ( event .getEntityName (), entity );
327391 final String entityName = persister .getEntityName ();
328-
329- Object id = getDetachedEntityId ( event , entity , persister );
392+ if ( originalId == null ) {
393+ originalId = persister .getIdentifier ( entity , source );
394+ }
395+ final Object clonedIdentifier ;
396+ if ( copiedId == null ) {
397+ clonedIdentifier = persister .getIdentifierType ().deepCopy ( originalId , source .getFactory () );
398+ }
399+ else {
400+ clonedIdentifier = copiedId ;
401+ }
402+ final Object id = getDetachedEntityId ( event , originalId , persister );
330403 // we must clone embedded composite identifiers, or we will get back the same instance that we pass in
331- final Object clonedIdentifier = persister .getIdentifierType ().deepCopy ( id , source .getFactory () );
332404 // apply the special MERGE fetch profile and perform the resolution (Session#get)
333405 final Object result = source .getLoadQueryInfluencers ().fromInternalFetchProfile (
334406 CascadingFetchProfile .MERGE ,
@@ -343,7 +415,7 @@ protected void entityIsDetached(MergeEvent event, MergeContext copyCache) {
343415 // we got here because we assumed that an instance
344416 // with an assigned id was detached, when it was
345417 // really persistent
346- entityIsTransient ( event , copyCache );
418+ entityIsTransient ( event , clonedIdentifier , copyCache );
347419 }
348420 else {
349421 // before cascade!
@@ -357,7 +429,6 @@ protected void entityIsDetached(MergeEvent event, MergeContext copyCache) {
357429 markInterceptorDirty ( entity , target );
358430 event .setResult ( result );
359431 }
360-
361432 }
362433
363434 private static Object targetEntity (MergeEvent event , Object entity , EntityPersister persister , Object id , Object result ) {
@@ -386,15 +457,15 @@ else if ( isVersionChanged( entity, source, persister, target ) ) {
386457 }
387458 }
388459
389- private static Object getDetachedEntityId (MergeEvent event , Object entity , EntityPersister persister ) {
460+ private static Object getDetachedEntityId (MergeEvent event , Object originalId , EntityPersister persister ) {
390461 final EventSource source = event .getSession ();
391462 final Object id = event .getRequestedId ();
392463 if ( id == null ) {
393- return persister . getIdentifier ( entity , source ) ;
464+ return originalId ;
394465 }
395466 else {
396467 // check that entity id = requestedId
397- Object entityId = persister . getIdentifier ( entity , source ) ;
468+ final Object entityId = originalId ;
398469 if ( !persister .getIdentifierType ().isEqual ( id , entityId , source .getFactory () ) ) {
399470 throw new HibernateException ( "merge requested with id not matching id of passed entity" );
400471 }
@@ -414,8 +485,10 @@ private static Object unproxyManagedForDetachedMerging(
414485 if ( isPersistentAttributeInterceptable ( incoming )
415486 && persister .getBytecodeEnhancementMetadata ().isEnhancedForLazyLoading () ) {
416487
417- final PersistentAttributeInterceptor incomingInterceptor = asPersistentAttributeInterceptable ( incoming ).$$_hibernate_getInterceptor ();
418- final PersistentAttributeInterceptor managedInterceptor = asPersistentAttributeInterceptable ( managed ).$$_hibernate_getInterceptor ();
488+ final PersistentAttributeInterceptor incomingInterceptor =
489+ asPersistentAttributeInterceptable ( incoming ).$$_hibernate_getInterceptor ();
490+ final PersistentAttributeInterceptor managedInterceptor =
491+ asPersistentAttributeInterceptable ( managed ).$$_hibernate_getInterceptor ();
419492
420493 // todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but
421494 // with different attributes initialized?
0 commit comments