25
25
import org .hibernate .engine .spi .PersistenceContext ;
26
26
import org .hibernate .engine .spi .PersistentAttributeInterceptor ;
27
27
import org .hibernate .engine .spi .SelfDirtinessTracker ;
28
+ import org .hibernate .engine .spi .SessionFactoryImplementor ;
28
29
import org .hibernate .engine .spi .SessionImplementor ;
29
30
import org .hibernate .event .spi .EntityCopyObserver ;
30
31
import org .hibernate .event .spi .EventSource ;
40
41
import org .hibernate .proxy .LazyInitializer ;
41
42
import org .hibernate .stat .spi .StatisticsImplementor ;
42
43
import org .hibernate .type .CollectionType ;
44
+ import org .hibernate .type .CompositeType ;
43
45
import org .hibernate .type .EntityType ;
44
46
import org .hibernate .type .ForeignKeyDirection ;
47
+ import org .hibernate .type .Type ;
45
48
import org .hibernate .type .TypeHelper ;
46
49
47
50
import static org .hibernate .engine .internal .ManagedTypeHelper .asPersistentAttributeInterceptable ;
48
51
import static org .hibernate .engine .internal .ManagedTypeHelper .asSelfDirtinessTracker ;
49
52
import static org .hibernate .engine .internal .ManagedTypeHelper .isHibernateProxy ;
50
53
import static org .hibernate .engine .internal .ManagedTypeHelper .isPersistentAttributeInterceptable ;
51
54
import static org .hibernate .engine .internal .ManagedTypeHelper .isSelfDirtinessTracker ;
55
+ import static org .hibernate .event .internal .EntityState .getEntityState ;
52
56
53
57
/**
54
58
* 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
144
148
}
145
149
146
150
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 ) {
148
207
case DETACHED :
149
- entityIsDetached (event , copiedAlready );
208
+ entityIsDetached ( event , copiedId , originalId , copiedAlready );
150
209
break ;
151
210
case TRANSIENT :
152
- entityIsTransient (event , copiedAlready );
211
+ entityIsTransient ( event , copiedId != null ? copiedId : originalId , copiedAlready );
153
212
break ;
154
213
case PERSISTENT :
155
- entityIsPersistent (event , copiedAlready );
214
+ entityIsPersistent ( event , copiedAlready );
156
215
break ;
157
216
default : //DELETED
158
217
if ( event .getSession ().getPersistenceContext ().getEntry ( entity ) == null ) {
@@ -165,7 +224,7 @@ private void merge(MergeEvent event, MergeContext copiedAlready, Object entity)
165
224
)
166
225
);
167
226
event .getSession ().getActionQueue ().unScheduleUnloadedDeletion ( entity );
168
- entityIsDetached (event , copiedAlready );
227
+ entityIsDetached (event , copiedId , originalId , copiedAlready );
169
228
break ;
170
229
}
171
230
throw new ObjectDeletedException (
@@ -176,30 +235,37 @@ private void merge(MergeEvent event, MergeContext copiedAlready, Object entity)
176
235
}
177
236
}
178
237
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 ) ;
199
258
}
200
259
}
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
+ }
201
266
}
202
- return EntityState .getEntityState ( entity , event .getEntityName (), entry , source , false );
267
+ compositeType .setPropertyValues ( idCopy , copyValues );
268
+ return idCopy ;
203
269
}
204
270
205
271
protected void entityIsPersistent (MergeEvent event , MergeContext copyCache ) {
@@ -214,14 +280,13 @@ protected void entityIsPersistent(MergeEvent event, MergeContext copyCache) {
214
280
event .setResult ( entity );
215
281
}
216
282
217
- protected void entityIsTransient (MergeEvent event , MergeContext copyCache ) {
283
+ protected void entityIsTransient (MergeEvent event , Object id , MergeContext copyCache ) {
218
284
LOG .trace ( "Merging transient instance" );
219
285
220
286
final Object entity = event .getEntity ();
221
287
final EventSource session = event .getSession ();
222
288
final String entityName = event .getEntityName ();
223
289
final EntityPersister persister = session .getEntityPersister ( entityName , entity );
224
- final Object id = persister .getIdentifier ( entity , session );
225
290
final Object copy = copyEntity ( copyCache , entity , session , persister , id );
226
291
227
292
// cascade first, so that all unsaved objects get their
@@ -231,7 +296,6 @@ protected void entityIsTransient(MergeEvent event, MergeContext copyCache) {
231
296
copyValues ( persister , entity , copy , session , copyCache , ForeignKeyDirection .FROM_PARENT );
232
297
233
298
saveTransientEntity ( copy , entityName , event .getRequestedId (), session , copyCache );
234
- persister .setIdentifier ( entity , persister .getIdentifier ( copy , session ), session );
235
299
236
300
// cascade first, so that all unsaved objects get their
237
301
// copy created before we actually copy
@@ -318,17 +382,25 @@ private void saveTransientEntity(
318
382
}
319
383
}
320
384
321
- protected void entityIsDetached (MergeEvent event , MergeContext copyCache ) {
385
+ protected void entityIsDetached (MergeEvent event , Object copiedId , Object originalId , MergeContext copyCache ) {
322
386
LOG .trace ( "Merging detached instance" );
323
387
324
388
final Object entity = event .getEntity ();
325
389
final EventSource source = event .getSession ();
326
390
final EntityPersister persister = source .getEntityPersister ( event .getEntityName (), entity );
327
391
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 );
330
403
// 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 () );
332
404
// apply the special MERGE fetch profile and perform the resolution (Session#get)
333
405
final Object result = source .getLoadQueryInfluencers ().fromInternalFetchProfile (
334
406
CascadingFetchProfile .MERGE ,
@@ -343,7 +415,7 @@ protected void entityIsDetached(MergeEvent event, MergeContext copyCache) {
343
415
// we got here because we assumed that an instance
344
416
// with an assigned id was detached, when it was
345
417
// really persistent
346
- entityIsTransient ( event , copyCache );
418
+ entityIsTransient ( event , clonedIdentifier , copyCache );
347
419
}
348
420
else {
349
421
// before cascade!
@@ -357,7 +429,6 @@ protected void entityIsDetached(MergeEvent event, MergeContext copyCache) {
357
429
markInterceptorDirty ( entity , target );
358
430
event .setResult ( result );
359
431
}
360
-
361
432
}
362
433
363
434
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 ) ) {
386
457
}
387
458
}
388
459
389
- private static Object getDetachedEntityId (MergeEvent event , Object entity , EntityPersister persister ) {
460
+ private static Object getDetachedEntityId (MergeEvent event , Object originalId , EntityPersister persister ) {
390
461
final EventSource source = event .getSession ();
391
462
final Object id = event .getRequestedId ();
392
463
if ( id == null ) {
393
- return persister . getIdentifier ( entity , source ) ;
464
+ return originalId ;
394
465
}
395
466
else {
396
467
// check that entity id = requestedId
397
- Object entityId = persister . getIdentifier ( entity , source ) ;
468
+ final Object entityId = originalId ;
398
469
if ( !persister .getIdentifierType ().isEqual ( id , entityId , source .getFactory () ) ) {
399
470
throw new HibernateException ( "merge requested with id not matching id of passed entity" );
400
471
}
@@ -414,8 +485,10 @@ private static Object unproxyManagedForDetachedMerging(
414
485
if ( isPersistentAttributeInterceptable ( incoming )
415
486
&& persister .getBytecodeEnhancementMetadata ().isEnhancedForLazyLoading () ) {
416
487
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 ();
419
492
420
493
// todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but
421
494
// with different attributes initialized?
0 commit comments