From ec3be767e479617a26bf111101e1faf5705b027c Mon Sep 17 00:00:00 2001 From: Gavin King Date: Mon, 2 Sep 2024 09:45:50 +0200 Subject: [PATCH] HHH-18553 handle case where managed entity was already removed just short-circuit and abort the remove() Signed-off-by: Gavin King --- .../internal/DefaultDeleteEventListener.java | 68 +++++++++++-------- .../deletedetached/DeleteDetachedTest.java | 14 ++++ 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java index d9c535fdcf..608993baa3 100644 --- a/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java +++ b/hibernate-core/src/main/java/org/hibernate/event/internal/DefaultDeleteEventListener.java @@ -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( diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/deletedetached/DeleteDetachedTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/deletedetached/DeleteDetachedTest.java index 3ad05e8c4d..c7f771607d 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/deletedetached/DeleteDetachedTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/deletedetached/DeleteDetachedTest.java @@ -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;