Skip to content

Commit 186bcc6

Browse files
dreab8beikov
authored andcommitted
HHH-17634 Merging a new entity having a @GeneratedValue id should not set the generated id of the original entity
1 parent 7751cc4 commit 186bcc6

File tree

1 file changed

+113
-40
lines changed

1 file changed

+113
-40
lines changed

hibernate-core/src/main/java/org/hibernate/event/internal/DefaultMergeEventListener.java

Lines changed: 113 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import org.hibernate.engine.spi.PersistenceContext;
2626
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
2727
import org.hibernate.engine.spi.SelfDirtinessTracker;
28+
import org.hibernate.engine.spi.SessionFactoryImplementor;
2829
import org.hibernate.engine.spi.SessionImplementor;
2930
import org.hibernate.event.spi.EntityCopyObserver;
3031
import org.hibernate.event.spi.EventSource;
@@ -40,15 +41,18 @@
4041
import org.hibernate.proxy.LazyInitializer;
4142
import org.hibernate.stat.spi.StatisticsImplementor;
4243
import org.hibernate.type.CollectionType;
44+
import org.hibernate.type.CompositeType;
4345
import org.hibernate.type.EntityType;
4446
import org.hibernate.type.ForeignKeyDirection;
47+
import org.hibernate.type.Type;
4548
import org.hibernate.type.TypeHelper;
4649

4750
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
4851
import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTracker;
4952
import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy;
5053
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
5154
import 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

Comments
 (0)