HHH-18553 flush/evict when there is a managed instance while deleting the detached instance
Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
parent
5c89079f2e
commit
09fa8ef76a
|
@ -597,7 +597,7 @@ public interface Session extends SharedSessionContract, EntityManager {
|
||||||
* The modes {@link LockMode#WRITE} and {@link LockMode#UPGRADE_SKIPLOCKED}
|
* The modes {@link LockMode#WRITE} and {@link LockMode#UPGRADE_SKIPLOCKED}
|
||||||
* are not legal arguments to {@code lock()}.
|
* are not legal arguments to {@code lock()}.
|
||||||
*
|
*
|
||||||
* @param object a persistent instance
|
* @param object a persistent instance associated with this session
|
||||||
* @param lockMode the lock level
|
* @param lockMode the lock level
|
||||||
*/
|
*/
|
||||||
void lock(Object object, LockMode lockMode);
|
void lock(Object object, LockMode lockMode);
|
||||||
|
@ -609,7 +609,7 @@ public interface Session extends SharedSessionContract, EntityManager {
|
||||||
* This operation cascades to associated instances if the association is
|
* This operation cascades to associated instances if the association is
|
||||||
* mapped with {@link org.hibernate.annotations.CascadeType#LOCK}.
|
* mapped with {@link org.hibernate.annotations.CascadeType#LOCK}.
|
||||||
*
|
*
|
||||||
* @param object a persistent instance
|
* @param object a persistent instance associated with this session
|
||||||
* @param lockOptions the lock options
|
* @param lockOptions the lock options
|
||||||
*
|
*
|
||||||
* @since 6.2
|
* @since 6.2
|
||||||
|
@ -664,8 +664,12 @@ public interface Session extends SharedSessionContract, EntityManager {
|
||||||
* Mark a persistence instance associated with this session for removal from
|
* Mark a persistence instance associated with this session for removal from
|
||||||
* the underlying database. Ths operation cascades to associated instances if
|
* the underlying database. Ths operation cascades to associated instances if
|
||||||
* the association is mapped {@link jakarta.persistence.CascadeType#REMOVE}.
|
* the association is mapped {@link jakarta.persistence.CascadeType#REMOVE}.
|
||||||
|
* <p>
|
||||||
|
* Except when operating in fully JPA-compliant mode, this operation does,
|
||||||
|
* contrary to the JPA specification, accept a detached entity instance.
|
||||||
*
|
*
|
||||||
* @param object the managed persistent instance to remove
|
* @param object the managed persistent instance to remove, or a detached
|
||||||
|
* instance unless operating in fully JPA-compliant mode
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
void remove(Object object);
|
void remove(Object object);
|
||||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.event.internal;
|
||||||
import org.hibernate.CacheMode;
|
import org.hibernate.CacheMode;
|
||||||
import org.hibernate.HibernateException;
|
import org.hibernate.HibernateException;
|
||||||
import org.hibernate.LockMode;
|
import org.hibernate.LockMode;
|
||||||
|
import org.hibernate.NonUniqueObjectException;
|
||||||
import org.hibernate.TransientObjectException;
|
import org.hibernate.TransientObjectException;
|
||||||
import org.hibernate.action.internal.CollectionRemoveAction;
|
import org.hibernate.action.internal.CollectionRemoveAction;
|
||||||
import org.hibernate.action.internal.EntityDeleteAction;
|
import org.hibernate.action.internal.EntityDeleteAction;
|
||||||
|
@ -44,7 +45,6 @@ import org.hibernate.persister.collection.CollectionPersister;
|
||||||
import org.hibernate.persister.entity.EntityPersister;
|
import org.hibernate.persister.entity.EntityPersister;
|
||||||
import org.hibernate.pretty.MessageHelper;
|
import org.hibernate.pretty.MessageHelper;
|
||||||
import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl;
|
import org.hibernate.property.access.internal.PropertyAccessStrategyBackRefImpl;
|
||||||
import org.hibernate.proxy.HibernateProxy;
|
|
||||||
import org.hibernate.proxy.LazyInitializer;
|
import org.hibernate.proxy.LazyInitializer;
|
||||||
import org.hibernate.type.CollectionType;
|
import org.hibernate.type.CollectionType;
|
||||||
import org.hibernate.type.ComponentType;
|
import org.hibernate.type.ComponentType;
|
||||||
|
@ -52,6 +52,7 @@ import org.hibernate.type.Type;
|
||||||
import org.hibernate.type.TypeHelper;
|
import org.hibernate.type.TypeHelper;
|
||||||
|
|
||||||
import static org.hibernate.engine.internal.Collections.skipRemoval;
|
import static org.hibernate.engine.internal.Collections.skipRemoval;
|
||||||
|
import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines the default delete event listener used by hibernate for deleting entities
|
* Defines the default delete event listener used by hibernate for deleting entities
|
||||||
|
@ -102,7 +103,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
|
||||||
|
|
||||||
private boolean optimizeUnloadedDelete(DeleteEvent event) {
|
private boolean optimizeUnloadedDelete(DeleteEvent event) {
|
||||||
final Object object = event.getObject();
|
final Object object = event.getObject();
|
||||||
final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( object );
|
final LazyInitializer lazyInitializer = extractLazyInitializer( object );
|
||||||
if ( lazyInitializer != null ) {
|
if ( lazyInitializer != null ) {
|
||||||
if ( lazyInitializer.isUninitialized() ) {
|
if ( lazyInitializer.isUninitialized() ) {
|
||||||
final EventSource source = event.getSession();
|
final EventSource source = event.getSession();
|
||||||
|
@ -157,54 +158,76 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
|
||||||
final Object entity = persistenceContext.unproxyAndReassociate( event.getObject() );
|
final Object entity = persistenceContext.unproxyAndReassociate( event.getObject() );
|
||||||
final EntityEntry entityEntry = persistenceContext.getEntry( entity );
|
final EntityEntry entityEntry = persistenceContext.getEntry( entity );
|
||||||
if ( entityEntry == null ) {
|
if ( entityEntry == null ) {
|
||||||
deleteTransientInstance( event, transientEntities, entity );
|
deleteUnmanagedInstance( event, transientEntities, entity );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
deletePersistentInstance( event, transientEntities, entity, entityEntry );
|
deletePersistentInstance( event, transientEntities, entity, entityEntry );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteTransientInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) {
|
private void deleteUnmanagedInstance(DeleteEvent event, DeleteContext transientEntities, Object entity) {
|
||||||
LOG.trace( "Entity was not persistent in delete processing" );
|
LOG.trace( "Deleted entity was not associated with current session" );
|
||||||
|
|
||||||
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 );
|
||||||
if ( ForeignKeys.isTransient( persister.getEntityName(), entity, null, source ) ) {
|
if ( ForeignKeys.isTransient( persister.getEntityName(), entity, null, source ) ) {
|
||||||
deleteTransientEntity( source, entity, persister, transientEntities );
|
deleteTransientEntity( source, entity, persister, transientEntities );
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
performDetachedEntityDeletionCheck( event );
|
performDetachedEntityDeletionCheck( event );
|
||||||
|
deleteDetachedEntity( event, transientEntities, entity, persister, source );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final Object id = persister.getIdentifier( entity, source );
|
private void deleteDetachedEntity(DeleteEvent event, DeleteContext transientEntities, Object entity, EntityPersister persister, EventSource source) {
|
||||||
if ( id == null ) {
|
final Object id = persister.getIdentifier( entity, source );
|
||||||
throw new TransientObjectException( "Cannot delete instance of entity '" + persister.getEntityName()
|
if ( id == null ) {
|
||||||
+ "' because it has a null identifier" );
|
throw new TransientObjectException( "Cannot delete instance of entity '"
|
||||||
|
+ persister.getEntityName() + "' because it has a null identifier" );
|
||||||
|
}
|
||||||
|
|
||||||
|
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
|
||||||
|
final EntityKey key = source.generateEntityKey( id, persister);
|
||||||
|
final Object version = persister.getVersion(entity);
|
||||||
|
|
||||||
|
// persistenceContext.checkUniqueness( key, entity );
|
||||||
|
flushAndEvictExistingEntity( key, version, persister, source );
|
||||||
|
|
||||||
|
new OnUpdateVisitor( source, id, entity ).process( entity, persister );
|
||||||
|
|
||||||
|
final EntityEntry entityEntry = persistenceContext.addEntity(
|
||||||
|
entity,
|
||||||
|
persister.isMutable() ? Status.MANAGED : Status.READ_ONLY,
|
||||||
|
persister.getValues(entity),
|
||||||
|
key,
|
||||||
|
version,
|
||||||
|
LockMode.NONE,
|
||||||
|
true,
|
||||||
|
persister,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
persister.afterReassociate(entity, source);
|
||||||
|
|
||||||
|
delete( event, transientEntities, source, entity, persister, id, version, entityEntry );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Since Hibernate 7, if a detached instance is passed to remove(),
|
||||||
|
* and if there is already an existing managed entity with the same
|
||||||
|
* id, flush and evict it, after checking that the versions match.
|
||||||
|
*/
|
||||||
|
private static void flushAndEvictExistingEntity(
|
||||||
|
EntityKey key, Object version, EntityPersister persister, EventSource source) {
|
||||||
|
final Object existingEntity = source.getPersistenceContextInternal().getEntity( key );
|
||||||
|
if ( existingEntity != null ) {
|
||||||
|
source.flush();
|
||||||
|
if ( !persister.isVersioned()
|
||||||
|
|| persister.getVersionType()
|
||||||
|
.isEqual( version, persister.getVersion( existingEntity ) ) ) {
|
||||||
|
source.evict( existingEntity );
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
throw new NonUniqueObjectException( key.getIdentifier(), key.getEntityName() );
|
||||||
}
|
}
|
||||||
|
|
||||||
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
|
|
||||||
final EntityKey key = source.generateEntityKey( id, persister );
|
|
||||||
|
|
||||||
persistenceContext.checkUniqueness( key, entity);
|
|
||||||
|
|
||||||
new OnUpdateVisitor( source, id, entity ).process( entity, persister );
|
|
||||||
|
|
||||||
final Object version = persister.getVersion( entity );
|
|
||||||
|
|
||||||
final EntityEntry entityEntry = persistenceContext.addEntity(
|
|
||||||
entity,
|
|
||||||
persister.isMutable() ? Status.MANAGED : Status.READ_ONLY,
|
|
||||||
persister.getValues( entity ),
|
|
||||||
key,
|
|
||||||
version,
|
|
||||||
LockMode.NONE,
|
|
||||||
true,
|
|
||||||
persister,
|
|
||||||
false
|
|
||||||
);
|
|
||||||
persister.afterReassociate( entity, source );
|
|
||||||
|
|
||||||
delete( event, transientEntities, source, entity, persister, id, version, entityEntry );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,7 +310,7 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasRegisteredRemoveCallbacks(EntityPersister persister) {
|
private boolean hasRegisteredRemoveCallbacks(EntityPersister persister) {
|
||||||
Class<?> mappedClass = persister.getMappedClass();
|
final Class<?> mappedClass = persister.getMappedClass();
|
||||||
return callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.PRE_REMOVE )
|
return callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.PRE_REMOVE )
|
||||||
|| callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE );
|
|| callbackRegistry.hasRegisteredCallbacks( mappedClass, CallbackType.POST_REMOVE );
|
||||||
}
|
}
|
||||||
|
@ -295,25 +318,23 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
|
||||||
/**
|
/**
|
||||||
* Called when we have recognized an attempt to delete a detached entity.
|
* Called when we have recognized an attempt to delete a detached entity.
|
||||||
* <p>
|
* <p>
|
||||||
* This is perfectly valid in Hibernate usage; JPA, however, forbids this.
|
* This is perfectly legal in regular Hibernate usage; the JPA spec,
|
||||||
* Thus, this is a hook for HEM to affect this behavior.
|
* however, forbids it.
|
||||||
*
|
|
||||||
* @param event The event.
|
|
||||||
*/
|
*/
|
||||||
protected void performDetachedEntityDeletionCheck(DeleteEvent event) {
|
protected void performDetachedEntityDeletionCheck(DeleteEvent event) {
|
||||||
if ( jpaBootstrap ) {
|
if ( jpaBootstrap ) {
|
||||||
disallowDeletionOfDetached( event );
|
disallowDeletionOfDetached( event );
|
||||||
}
|
}
|
||||||
// ok in normal Hibernate usage to delete a detached entity; JPA however
|
|
||||||
// forbids it, thus this is a hook for HEM to affect this behavior
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void disallowDeletionOfDetached(DeleteEvent event) {
|
private void disallowDeletionOfDetached(DeleteEvent event) {
|
||||||
EventSource source = event.getSession();
|
final EventSource source = event.getSession();
|
||||||
String entityName = event.getEntityName();
|
final String explicitEntityName = event.getEntityName();
|
||||||
EntityPersister persister = source.getEntityPersister( entityName, event.getObject() );
|
final EntityPersister persister = source.getEntityPersister( explicitEntityName, event.getObject() );
|
||||||
Object id = persister.getIdentifier( event.getObject(), source );
|
final Object id = persister.getIdentifier( event.getObject(), source );
|
||||||
entityName = entityName == null ? source.guessEntityName( event.getObject() ) : entityName;
|
final String entityName = explicitEntityName == null
|
||||||
|
? source.guessEntityName( event.getObject() )
|
||||||
|
: explicitEntityName;
|
||||||
throw new IllegalArgumentException( "Given entity is not associated with the persistence context [" + entityName + "#" + id + "]" );
|
throw new IllegalArgumentException( "Given entity is not associated with the persistence context [" + entityName + "#" + id + "]" );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@ import org.hibernate.CacheMode;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
import org.hibernate.Session;
|
import org.hibernate.Session;
|
||||||
import org.hibernate.Transaction;
|
import org.hibernate.Transaction;
|
||||||
import org.hibernate.TransientObjectException;
|
|
||||||
import org.hibernate.UnresolvableObjectException;
|
import org.hibernate.UnresolvableObjectException;
|
||||||
import org.hibernate.cfg.AvailableSettings;
|
import org.hibernate.cfg.AvailableSettings;
|
||||||
import org.hibernate.engine.spi.SessionImplementor;
|
import org.hibernate.engine.spi.SessionImplementor;
|
||||||
|
|
Loading…
Reference in New Issue