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 Object version = persister.getVersion(entity);
final Object version = persister.getVersion( 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 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);
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
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 );
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.
*
* @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) {
final Object existingEntity = source.getPersistenceContextInternal().getEntity( key );
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
final Object existingEntity = persistenceContext.getEntity( key );
if ( existingEntity != null ) {
LOG.flushAndEvictOnRemove( key.getEntityName() );
source.flush();
if ( !persister.isVersioned()
|| persister.getVersionType()
.isEqual( version, persister.getVersion( existingEntity ) ) ) {
source.evict( existingEntity );
if ( persistenceContext.getEntry( existingEntity ).getStatus().isDeletedOrGone() ) {
// already deleted, no work to do
return true;
}
else {
throw new StaleObjectStateException( key.getEntityName(), key.getIdentifier(),
"Persistence context contains a more recent version of the given entity" );
LOG.flushAndEvictOnRemove( key.getEntityName() );
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(

View File

@ -56,6 +56,20 @@ public class DeleteDetachedTest {
});
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
static class Thing {
@GeneratedValue @Id long id;