HHH-17634 Merging a new entity having a @GeneratedValue id should not set the generated id of the original entity
This commit is contained in:
parent
7751cc4491
commit
186bcc6ac8
|
@ -25,6 +25,7 @@ import org.hibernate.engine.spi.EntityKey;
|
||||||
import org.hibernate.engine.spi.PersistenceContext;
|
import org.hibernate.engine.spi.PersistenceContext;
|
||||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||||
|
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
import org.hibernate.event.spi.EntityCopyObserver;
|
import org.hibernate.event.spi.EntityCopyObserver;
|
||||||
import org.hibernate.event.spi.EventSource;
|
import org.hibernate.event.spi.EventSource;
|
||||||
|
@ -40,8 +41,10 @@ import org.hibernate.proxy.HibernateProxy;
|
||||||
import org.hibernate.proxy.LazyInitializer;
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
import org.hibernate.stat.spi.StatisticsImplementor;
|
import org.hibernate.stat.spi.StatisticsImplementor;
|
||||||
import org.hibernate.type.CollectionType;
|
import org.hibernate.type.CollectionType;
|
||||||
|
import org.hibernate.type.CompositeType;
|
||||||
import org.hibernate.type.EntityType;
|
import org.hibernate.type.EntityType;
|
||||||
import org.hibernate.type.ForeignKeyDirection;
|
import org.hibernate.type.ForeignKeyDirection;
|
||||||
|
import org.hibernate.type.Type;
|
||||||
import org.hibernate.type.TypeHelper;
|
import org.hibernate.type.TypeHelper;
|
||||||
|
|
||||||
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
|
import static org.hibernate.engine.internal.ManagedTypeHelper.asPersistentAttributeInterceptable;
|
||||||
|
@ -49,6 +52,7 @@ import static org.hibernate.engine.internal.ManagedTypeHelper.asSelfDirtinessTra
|
||||||
import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy;
|
import static org.hibernate.engine.internal.ManagedTypeHelper.isHibernateProxy;
|
||||||
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
|
import static org.hibernate.engine.internal.ManagedTypeHelper.isPersistentAttributeInterceptable;
|
||||||
import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker;
|
import static org.hibernate.engine.internal.ManagedTypeHelper.isSelfDirtinessTracker;
|
||||||
|
import static org.hibernate.event.internal.EntityState.getEntityState;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the default copy event listener used by hibernate for copying entities
|
* Defines the default copy event listener used by hibernate for copying entities
|
||||||
|
@ -144,15 +148,70 @@ public class DefaultMergeEventListener
|
||||||
}
|
}
|
||||||
|
|
||||||
private void merge(MergeEvent event, MergeContext copiedAlready, Object entity) {
|
private void merge(MergeEvent event, MergeContext copiedAlready, Object entity) {
|
||||||
switch ( entityState( event, entity ) ) {
|
final EventSource source = event.getSession();
|
||||||
|
// Check the persistence context for an entry relating to this
|
||||||
|
// entity to be merged...
|
||||||
|
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
|
||||||
|
EntityEntry entry = persistenceContext.getEntry( entity );
|
||||||
|
final EntityState entityState;
|
||||||
|
final Object copiedId;
|
||||||
|
final Object originalId;
|
||||||
|
if ( entry == null ) {
|
||||||
|
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
||||||
|
originalId = persister.getIdentifier( entity, source );
|
||||||
|
if ( originalId != null ) {
|
||||||
|
final EntityKey entityKey;
|
||||||
|
if ( persister.getIdentifierType().isComponentType() ) {
|
||||||
|
/*
|
||||||
|
this is needed in case of composite id containing an association with a generated identifier, in such a case
|
||||||
|
generating the EntityKey will cause a NPE when trying to get the hashcode of the null id
|
||||||
|
*/
|
||||||
|
copiedId = copyCompositeTypeId(
|
||||||
|
originalId,
|
||||||
|
(CompositeType) persister.getIdentifierType(),
|
||||||
|
source,
|
||||||
|
copiedAlready
|
||||||
|
);
|
||||||
|
entityKey = source.generateEntityKey( copiedId, persister );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
copiedId = null;
|
||||||
|
entityKey = source.generateEntityKey( originalId, persister );
|
||||||
|
}
|
||||||
|
final Object managedEntity = persistenceContext.getEntity( entityKey );
|
||||||
|
entry = persistenceContext.getEntry( managedEntity );
|
||||||
|
if ( entry != null ) {
|
||||||
|
// we have a special case of a detached entity from the
|
||||||
|
// perspective of the merge operation. Specifically, we have
|
||||||
|
// an incoming entity instance which has a corresponding
|
||||||
|
// entry in the current persistence context, but registered
|
||||||
|
// under a different entity instance
|
||||||
|
entityState = EntityState.DETACHED;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
entityState = getEntityState( entity, event.getEntityName(), entry, source, false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
copiedId = null;
|
||||||
|
entityState = getEntityState( entity, event.getEntityName(), entry, source, false );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
copiedId = null;
|
||||||
|
originalId = null;
|
||||||
|
entityState = getEntityState( entity, event.getEntityName(), entry, source, false );
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ( entityState ) {
|
||||||
case DETACHED:
|
case DETACHED:
|
||||||
entityIsDetached(event, copiedAlready);
|
entityIsDetached( event, copiedId, originalId, copiedAlready );
|
||||||
break;
|
break;
|
||||||
case TRANSIENT:
|
case TRANSIENT:
|
||||||
entityIsTransient(event, copiedAlready);
|
entityIsTransient( event, copiedId != null ? copiedId : originalId, copiedAlready );
|
||||||
break;
|
break;
|
||||||
case PERSISTENT:
|
case PERSISTENT:
|
||||||
entityIsPersistent(event, copiedAlready);
|
entityIsPersistent( event, copiedAlready );
|
||||||
break;
|
break;
|
||||||
default: //DELETED
|
default: //DELETED
|
||||||
if ( event.getSession().getPersistenceContext().getEntry( entity ) == null ) {
|
if ( event.getSession().getPersistenceContext().getEntry( entity ) == null ) {
|
||||||
|
@ -165,7 +224,7 @@ public class DefaultMergeEventListener
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
event.getSession().getActionQueue().unScheduleUnloadedDeletion( entity );
|
event.getSession().getActionQueue().unScheduleUnloadedDeletion( entity );
|
||||||
entityIsDetached(event, copiedAlready);
|
entityIsDetached(event, copiedId, originalId, copiedAlready);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
throw new ObjectDeletedException(
|
throw new ObjectDeletedException(
|
||||||
|
@ -176,30 +235,37 @@ public class DefaultMergeEventListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static EntityState entityState(MergeEvent event, Object entity) {
|
private static Object copyCompositeTypeId(
|
||||||
final EventSource source = event.getSession();
|
Object id,
|
||||||
// Check the persistence context for an entry relating to this
|
CompositeType compositeType,
|
||||||
// entity to be merged...
|
EventSource session,
|
||||||
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
|
MergeContext mergeContext) {
|
||||||
EntityEntry entry = persistenceContext.getEntry( entity );
|
final SessionFactoryImplementor sessionFactory = session.getSessionFactory();
|
||||||
if ( entry == null ) {
|
final Object idCopy = compositeType.deepCopy( id, sessionFactory );
|
||||||
EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
final Type[] subtypes = compositeType.getSubtypes();
|
||||||
Object id = persister.getIdentifier( entity, source );
|
final Object[] propertyValues = compositeType.getPropertyValues( id );
|
||||||
if ( id != null ) {
|
final Object[] copyValues = compositeType.getPropertyValues( idCopy );
|
||||||
final EntityKey entityKey = source.generateEntityKey( id, persister );
|
for ( int i = 0; i < subtypes.length; i++ ) {
|
||||||
final Object managedEntity = persistenceContext.getEntity( entityKey );
|
final Type subtype = subtypes[i];
|
||||||
entry = persistenceContext.getEntry( managedEntity );
|
if ( subtype.isEntityType() ) {
|
||||||
if ( entry != null ) {
|
// the value of the copy in the MergeContext has the id assigned
|
||||||
// we have a special case of a detached entity from the
|
final Object o = mergeContext.get( propertyValues[i] );
|
||||||
// perspective of the merge operation. Specifically, we have
|
if ( o != null ) {
|
||||||
// an incoming entity instance which has a corresponding
|
copyValues[i] = o;
|
||||||
// entry in the current persistence context, but registered
|
}
|
||||||
// under a different entity instance
|
else {
|
||||||
return EntityState.DETACHED;
|
copyValues[i] = subtype.deepCopy( propertyValues[i], sessionFactory );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if ( subtype.isComponentType() ) {
|
||||||
|
copyValues[i] = copyCompositeTypeId( propertyValues[i], (CompositeType) subtype, session, mergeContext );
|
||||||
}
|
}
|
||||||
return EntityState.getEntityState( entity, event.getEntityName(), entry, source, false );
|
else {
|
||||||
|
copyValues[i] = subtype.deepCopy( propertyValues[i], sessionFactory );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compositeType.setPropertyValues( idCopy, copyValues );
|
||||||
|
return idCopy;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void entityIsPersistent(MergeEvent event, MergeContext copyCache) {
|
protected void entityIsPersistent(MergeEvent event, MergeContext copyCache) {
|
||||||
|
@ -214,14 +280,13 @@ public class DefaultMergeEventListener
|
||||||
event.setResult( entity );
|
event.setResult( entity );
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void entityIsTransient(MergeEvent event, MergeContext copyCache) {
|
protected void entityIsTransient(MergeEvent event, Object id, MergeContext copyCache) {
|
||||||
LOG.trace( "Merging transient instance" );
|
LOG.trace( "Merging transient instance" );
|
||||||
|
|
||||||
final Object entity = event.getEntity();
|
final Object entity = event.getEntity();
|
||||||
final EventSource session = event.getSession();
|
final EventSource session = event.getSession();
|
||||||
final String entityName = event.getEntityName();
|
final String entityName = event.getEntityName();
|
||||||
final EntityPersister persister = session.getEntityPersister( entityName, entity );
|
final EntityPersister persister = session.getEntityPersister( entityName, entity );
|
||||||
final Object id = persister.getIdentifier( entity, session );
|
|
||||||
final Object copy = copyEntity( copyCache, entity, session, persister, id );
|
final Object copy = copyEntity( copyCache, entity, session, persister, id );
|
||||||
|
|
||||||
// cascade first, so that all unsaved objects get their
|
// cascade first, so that all unsaved objects get their
|
||||||
|
@ -231,7 +296,6 @@ public class DefaultMergeEventListener
|
||||||
copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.FROM_PARENT );
|
copyValues( persister, entity, copy, session, copyCache, ForeignKeyDirection.FROM_PARENT );
|
||||||
|
|
||||||
saveTransientEntity( copy, entityName, event.getRequestedId(), session, copyCache );
|
saveTransientEntity( copy, entityName, event.getRequestedId(), session, copyCache );
|
||||||
persister.setIdentifier( entity, persister.getIdentifier( copy, session ), session );
|
|
||||||
|
|
||||||
// cascade first, so that all unsaved objects get their
|
// cascade first, so that all unsaved objects get their
|
||||||
// copy created before we actually copy
|
// copy created before we actually copy
|
||||||
|
@ -318,17 +382,25 @@ public class DefaultMergeEventListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void entityIsDetached(MergeEvent event, MergeContext copyCache) {
|
protected void entityIsDetached(MergeEvent event, Object copiedId, Object originalId, MergeContext copyCache) {
|
||||||
LOG.trace( "Merging detached instance" );
|
LOG.trace( "Merging detached instance" );
|
||||||
|
|
||||||
final Object entity = event.getEntity();
|
final Object entity = event.getEntity();
|
||||||
final EventSource source = event.getSession();
|
final EventSource source = event.getSession();
|
||||||
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
||||||
final String entityName = persister.getEntityName();
|
final String entityName = persister.getEntityName();
|
||||||
|
if ( originalId == null ) {
|
||||||
Object id = getDetachedEntityId( event, entity, persister );
|
originalId = persister.getIdentifier( entity, source );
|
||||||
|
}
|
||||||
|
final Object clonedIdentifier;
|
||||||
|
if ( copiedId == null ) {
|
||||||
|
clonedIdentifier = persister.getIdentifierType().deepCopy( originalId, source.getFactory() );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
clonedIdentifier = copiedId;
|
||||||
|
}
|
||||||
|
final Object id = getDetachedEntityId( event, originalId, persister );
|
||||||
// we must clone embedded composite identifiers, or we will get back the same instance that we pass in
|
// we must clone embedded composite identifiers, or we will get back the same instance that we pass in
|
||||||
final Object clonedIdentifier = persister.getIdentifierType().deepCopy( id, source.getFactory() );
|
|
||||||
// apply the special MERGE fetch profile and perform the resolution (Session#get)
|
// apply the special MERGE fetch profile and perform the resolution (Session#get)
|
||||||
final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(
|
final Object result = source.getLoadQueryInfluencers().fromInternalFetchProfile(
|
||||||
CascadingFetchProfile.MERGE,
|
CascadingFetchProfile.MERGE,
|
||||||
|
@ -343,7 +415,7 @@ public class DefaultMergeEventListener
|
||||||
// we got here because we assumed that an instance
|
// we got here because we assumed that an instance
|
||||||
// with an assigned id was detached, when it was
|
// with an assigned id was detached, when it was
|
||||||
// really persistent
|
// really persistent
|
||||||
entityIsTransient( event, copyCache );
|
entityIsTransient( event, clonedIdentifier, copyCache );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// before cascade!
|
// before cascade!
|
||||||
|
@ -357,7 +429,6 @@ public class DefaultMergeEventListener
|
||||||
markInterceptorDirty( entity, target );
|
markInterceptorDirty( entity, target );
|
||||||
event.setResult( result );
|
event.setResult( result );
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object targetEntity(MergeEvent event, Object entity, EntityPersister persister, Object id, Object result) {
|
private static Object targetEntity(MergeEvent event, Object entity, EntityPersister persister, Object id, Object result) {
|
||||||
|
@ -386,15 +457,15 @@ public class DefaultMergeEventListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Object getDetachedEntityId(MergeEvent event, Object entity, EntityPersister persister) {
|
private static Object getDetachedEntityId(MergeEvent event, Object originalId, EntityPersister persister) {
|
||||||
final EventSource source = event.getSession();
|
final EventSource source = event.getSession();
|
||||||
final Object id = event.getRequestedId();
|
final Object id = event.getRequestedId();
|
||||||
if ( id == null ) {
|
if ( id == null ) {
|
||||||
return persister.getIdentifier( entity, source );
|
return originalId;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// check that entity id = requestedId
|
// check that entity id = requestedId
|
||||||
Object entityId = persister.getIdentifier( entity, source );
|
final Object entityId = originalId;
|
||||||
if ( !persister.getIdentifierType().isEqual( id, entityId, source.getFactory() ) ) {
|
if ( !persister.getIdentifierType().isEqual( id, entityId, source.getFactory() ) ) {
|
||||||
throw new HibernateException( "merge requested with id not matching id of passed entity" );
|
throw new HibernateException( "merge requested with id not matching id of passed entity" );
|
||||||
}
|
}
|
||||||
|
@ -414,8 +485,10 @@ public class DefaultMergeEventListener
|
||||||
if ( isPersistentAttributeInterceptable( incoming )
|
if ( isPersistentAttributeInterceptable( incoming )
|
||||||
&& persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) {
|
&& persister.getBytecodeEnhancementMetadata().isEnhancedForLazyLoading() ) {
|
||||||
|
|
||||||
final PersistentAttributeInterceptor incomingInterceptor = asPersistentAttributeInterceptable( incoming ).$$_hibernate_getInterceptor();
|
final PersistentAttributeInterceptor incomingInterceptor =
|
||||||
final PersistentAttributeInterceptor managedInterceptor = asPersistentAttributeInterceptable( managed ).$$_hibernate_getInterceptor();
|
asPersistentAttributeInterceptable( incoming ).$$_hibernate_getInterceptor();
|
||||||
|
final PersistentAttributeInterceptor managedInterceptor =
|
||||||
|
asPersistentAttributeInterceptable( managed ).$$_hibernate_getInterceptor();
|
||||||
|
|
||||||
// todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but
|
// todo - do we need to specially handle the case where both `incoming` and `managed` are initialized, but
|
||||||
// with different attributes initialized?
|
// with different attributes initialized?
|
||||||
|
|
Loading…
Reference in New Issue