HHH-18553 handle case where managed entity was already removed

just short-circuit and abort the remove()

Signed-off-by: Gavin King <gavin@hibernate.org>
This commit is contained in:
Gavin King 2024-09-02 09:45:50 +02:00
parent edf813083e
commit ec3be767e4
2 changed files with 55 additions and 27 deletions

View File

@ -176,51 +176,65 @@ public class DefaultDeleteEventListener implements DeleteEventListener, Callback
} }
final EntityKey key = source.generateEntityKey( id, persister); final EntityKey key = source.generateEntityKey( id, persister);
final Object version = persister.getVersion(entity); final Object version = persister.getVersion( entity );
// persistenceContext.checkUniqueness( key, entity ); // persistenceContext.checkUniqueness( key, entity );
flushAndEvictExistingEntity( key, version, persister, source ); if ( !flushAndEvictExistingEntity( key, version, persister, source ) ) {
new OnUpdateVisitor( source, id, entity ).process( entity, persister ); new OnUpdateVisitor( source, id, entity ).process( entity, persister );
final PersistenceContext persistenceContext = source.getPersistenceContextInternal(); final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
final EntityEntry entityEntry = persistenceContext.addEntity( final EntityEntry entityEntry = persistenceContext.addEntity(
entity, entity,
persister.isMutable() ? Status.MANAGED : Status.READ_ONLY, persister.isMutable() ? Status.MANAGED : Status.READ_ONLY,
persister.getValues(entity), persister.getValues(entity),
key, key,
version, version,
LockMode.NONE, LockMode.NONE,
true, true,
persister, persister,
false false
); );
persister.afterReassociate(entity, source); persister.afterReassociate( entity, source );
delete( event, transientEntities, source, entity, persister, id, version, entityEntry ); delete( event, transientEntities, source, entity, persister, id, version, entityEntry );
}
} }
/** /**
* Since Hibernate 7, if a detached instance is passed to remove(), * Since Hibernate 7, if a detached instance is passed to remove(),
* and if there is already an existing managed entity with the same * and if there is already an existing managed entity with the same
* id, flush and evict it, after checking that the versions match. * id, flush and evict it, after checking that the versions match.
*
* @return true if the managed entity was already deleted
*/ */
private static void flushAndEvictExistingEntity( private static boolean flushAndEvictExistingEntity(
EntityKey key, Object version, EntityPersister persister, EventSource source) { EntityKey key, Object version, EntityPersister persister, EventSource source) {
final Object existingEntity = source.getPersistenceContextInternal().getEntity( key ); final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
final Object existingEntity = persistenceContext.getEntity( key );
if ( existingEntity != null ) { if ( existingEntity != null ) {
LOG.flushAndEvictOnRemove( key.getEntityName() ); if ( persistenceContext.getEntry( existingEntity ).getStatus().isDeletedOrGone() ) {
source.flush(); // already deleted, no work to do
if ( !persister.isVersioned() return true;
|| persister.getVersionType()
.isEqual( version, persister.getVersion( existingEntity ) ) ) {
source.evict( existingEntity );
} }
else { else {
throw new StaleObjectStateException( key.getEntityName(), key.getIdentifier(), LOG.flushAndEvictOnRemove( key.getEntityName() );
"Persistence context contains a more recent version of the given entity" ); source.flush();
if ( !persister.isVersioned()
|| persister.getVersionType()
.isEqual( version, persister.getVersion( existingEntity ) ) ) {
source.evict( existingEntity );
return false;
}
else {
throw new StaleObjectStateException( key.getEntityName(), key.getIdentifier(),
"Persistence context contains a more recent version of the given entity" );
}
} }
} }
else {
return false;
}
} }
private void deletePersistentInstance( private void deletePersistentInstance(

View File

@ -56,6 +56,20 @@ public class DeleteDetachedTest {
}); });
scope.inTransaction(s -> assertNotNull(s.find(Thing.class, thing.id))); scope.inTransaction(s -> assertNotNull(s.find(Thing.class, thing.id)));
} }
@Test void testAlreadyRemoved(SessionFactoryScope scope) {
Thing thing = new Thing();
thing.stuff = "Some stuff about the thing";
scope.inTransaction(s -> s.persist(thing));
scope.inTransaction(s -> {
Thing otherThing = s.find(Thing.class, thing.id);
assertNotNull(otherThing);
s.remove(otherThing);
s.remove(thing);
assertFalse(s.contains(thing));
assertFalse(s.contains(otherThing));
});
scope.inTransaction(s -> assertNull(s.find(Thing.class, thing.id)));
}
@Entity @Entity
static class Thing { static class Thing {
@GeneratedValue @Id long id; @GeneratedValue @Id long id;