HHH-16690 Fix re-saving for unloaded deletes
This commit is contained in:
parent
1602057721
commit
3f54b1261b
|
@ -1867,6 +1867,17 @@ public class StatefulPersistenceContext implements PersistenceContext {
|
|||
deletedUnloadedEntityKeys.add( key );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeDeletedUnloadedEntityKey(EntityKey key) {
|
||||
assert deletedUnloadedEntityKeys != null;
|
||||
deletedUnloadedEntityKeys.remove( key );
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsDeletedUnloadedEntityKeys() {
|
||||
return deletedUnloadedEntityKeys != null && !deletedUnloadedEntityKeys.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCollectionEntriesSize() {
|
||||
return collectionEntries == null ? 0 : collectionEntries.size();
|
||||
|
|
|
@ -50,6 +50,7 @@ import org.hibernate.internal.util.StringHelper;
|
|||
import org.hibernate.internal.util.collections.CollectionHelper;
|
||||
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
|
||||
import org.hibernate.metamodel.mapping.internal.EntityCollectionPart;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
import org.hibernate.type.CollectionType;
|
||||
|
@ -846,6 +847,26 @@ public class ActionQueue {
|
|||
|| ( collectionCreations != null && !collectionCreations.isEmpty() );
|
||||
}
|
||||
|
||||
public void unScheduleUnloadedDeletion(Object newEntity) {
|
||||
final EntityPersister entityPersister = session.getEntityPersister( null, newEntity );
|
||||
final Object identifier = entityPersister.getIdentifier( newEntity, session );
|
||||
if ( deletions != null ) {
|
||||
for ( int i = 0; i < deletions.size(); i++ ) {
|
||||
EntityDeleteAction action = deletions.get( i );
|
||||
if ( action.getInstance() == null
|
||||
&& action.getEntityName().equals( entityPersister.getEntityName() )
|
||||
&& entityPersister.getIdentifierMapping().areEqual( action.getId(), identifier, session ) ) {
|
||||
session.getPersistenceContextInternal().removeDeletedUnloadedEntityKey(
|
||||
session.generateEntityKey( identifier, entityPersister )
|
||||
);
|
||||
deletions.remove( i );
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new AssertionFailure( "Unable to perform un-delete for unloaded entity delete " + entityPersister.getEntityName() );
|
||||
}
|
||||
|
||||
public void unScheduleDeletion(EntityEntry entry, Object rescuedEntity) {
|
||||
final LazyInitializer lazyInitializer = HibernateProxy.extractLazyInitializer( rescuedEntity );
|
||||
if ( lazyInitializer != null ) {
|
||||
|
|
|
@ -757,6 +757,10 @@ public interface PersistenceContext {
|
|||
|
||||
void registerDeletedUnloadedEntityKey(EntityKey key);
|
||||
|
||||
void removeDeletedUnloadedEntityKey(EntityKey key);
|
||||
|
||||
boolean containsDeletedUnloadedEntityKeys();
|
||||
|
||||
/**
|
||||
* The size of the internal map storing all collection entries.
|
||||
* (The map is not exposed directly, but the size is often useful)
|
||||
|
|
|
@ -1147,6 +1147,11 @@ public class SessionDelegatorBaseImpl implements SessionImplementor {
|
|||
delegate.forceFlush( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceFlush(EntityKey e) throws HibernateException {
|
||||
delegate.forceFlush( e );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void merge(String entityName, Object object, MergeContext copiedAlready) throws HibernateException {
|
||||
delegate.merge( entityName, object, copiedAlready );
|
||||
|
|
|
@ -87,6 +87,10 @@ public interface SessionImplementor extends Session, SharedSessionContractImplem
|
|||
* Initiate a flush to force deletion of a re-persisted entity.
|
||||
*/
|
||||
void forceFlush(EntityEntry e) throws HibernateException;
|
||||
/**
|
||||
* Initiate a flush to force deletion of a re-persisted entity.
|
||||
*/
|
||||
void forceFlush(EntityKey e) throws HibernateException;
|
||||
|
||||
/**
|
||||
* Cascade the lock operation to the given child entity.
|
||||
|
|
|
@ -202,6 +202,9 @@ public abstract class AbstractSaveEventListener<C>
|
|||
throw new NonUniqueObjectException( id, persister.getEntityName() );
|
||||
}
|
||||
}
|
||||
else if ( persistenceContext.containsDeletedUnloadedEntityKey( key ) ) {
|
||||
source.forceFlush( key );
|
||||
}
|
||||
persister.setIdentifier( entity, id, source );
|
||||
return key;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,6 @@ import org.hibernate.engine.spi.EntityKey;
|
|||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.PersistentAttributeInterceptor;
|
||||
import org.hibernate.engine.spi.SelfDirtinessTracker;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.event.spi.EntityCopyObserver;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
|
@ -156,6 +155,19 @@ public class DefaultMergeEventListener
|
|||
entityIsPersistent(event, copiedAlready);
|
||||
break;
|
||||
default: //DELETED
|
||||
if ( event.getSession().getPersistenceContext().getEntry( entity ) == null ) {
|
||||
assert event.getSession().getPersistenceContext().containsDeletedUnloadedEntityKey(
|
||||
event.getSession().generateEntityKey(
|
||||
event.getSession()
|
||||
.getEntityPersister( event.getEntityName(), entity )
|
||||
.getIdentifier( entity, event.getSession() ),
|
||||
event.getSession().getEntityPersister( event.getEntityName(), entity )
|
||||
)
|
||||
);
|
||||
event.getSession().getActionQueue().unScheduleUnloadedDeletion( entity );
|
||||
entityIsDetached(event, copiedAlready);
|
||||
break;
|
||||
}
|
||||
throw new ObjectDeletedException(
|
||||
"deleted instance passed to merge",
|
||||
null,
|
||||
|
@ -174,7 +186,8 @@ public class DefaultMergeEventListener
|
|||
EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
||||
Object id = persister.getIdentifier( entity, source );
|
||||
if ( id != null ) {
|
||||
final Object managedEntity = persistenceContext.getEntity( source.generateEntityKey( id, persister ) );
|
||||
final EntityKey entityKey = source.generateEntityKey( id, 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
|
||||
|
|
|
@ -8,10 +8,13 @@ package org.hibernate.event.internal;
|
|||
|
||||
import org.hibernate.engine.internal.ForeignKeys;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.PersistenceContext;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.engine.spi.Status;
|
||||
import org.hibernate.internal.CoreLogging;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
public enum EntityState {
|
||||
PERSISTENT, TRANSIENT, DETACHED, DELETED;
|
||||
|
@ -65,6 +68,16 @@ public enum EntityState {
|
|||
if ( LOG.isTraceEnabled() ) {
|
||||
LOG.tracev( "Detached instance of: {0}", EventUtil.getLoggableName( entityName, entity ) );
|
||||
}
|
||||
|
||||
final PersistenceContext persistenceContext = source.getPersistenceContextInternal();
|
||||
if ( persistenceContext.containsDeletedUnloadedEntityKeys() ) {
|
||||
final EntityPersister entityPersister = source.getEntityPersister( entityName, entity );
|
||||
final Object identifier = entityPersister.getIdentifier( entity, source );
|
||||
final EntityKey entityKey = source.generateEntityKey( identifier, entityPersister );
|
||||
if ( persistenceContext.containsDeletedUnloadedEntityKey( entityKey ) ) {
|
||||
return EntityState.DELETED;
|
||||
}
|
||||
}
|
||||
return DETACHED;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ package org.hibernate.event.spi;
|
|||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.engine.spi.ActionQueue;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.EntityKey;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
|
||||
|
@ -32,6 +33,10 @@ public interface EventSource extends SessionImplementor {
|
|||
* Force an immediate flush
|
||||
*/
|
||||
void forceFlush(EntityEntry e) throws HibernateException;
|
||||
/**
|
||||
* Force an immediate flush
|
||||
*/
|
||||
void forceFlush(EntityKey e) throws HibernateException;
|
||||
|
||||
/**
|
||||
* Cascade merge an entity instance
|
||||
|
|
|
@ -1425,18 +1425,23 @@ public class SessionImpl
|
|||
|
||||
@Override
|
||||
public void forceFlush(EntityEntry entityEntry) throws HibernateException {
|
||||
forceFlush( entityEntry.getEntityKey() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forceFlush(EntityKey key) throws HibernateException {
|
||||
if ( log.isDebugEnabled() ) {
|
||||
log.debugf(
|
||||
"Flushing to force deletion of re-saved object: %s",
|
||||
MessageHelper.infoString( entityEntry.getPersister(), entityEntry.getId(), getFactory() )
|
||||
MessageHelper.infoString( key.getPersister(), key.getIdentifier(), getFactory() )
|
||||
);
|
||||
}
|
||||
|
||||
if ( persistenceContext.getCascadeLevel() > 0 ) {
|
||||
throw new ObjectDeletedException(
|
||||
"deleted object would be re-saved by cascade (remove deleted object from associations)",
|
||||
entityEntry.getId(),
|
||||
entityEntry.getPersister().getEntityName()
|
||||
key.getIdentifier(),
|
||||
key.getPersister().getEntityName()
|
||||
);
|
||||
}
|
||||
checkOpenOrWaitingForAutoClose();
|
||||
|
|
|
@ -2,15 +2,19 @@ package org.hibernate.orm.test.deleteunloaded;
|
|||
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.JiraKey;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hibernate.Hibernate.isInitialized;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
|
||||
@DomainModel( annotatedClasses = { Parent.class, Child.class } )
|
||||
@DomainModel( annotatedClasses = { Parent.class, Child.class, ParentSub.class } )
|
||||
@SessionFactory
|
||||
//@ServiceRegistry(
|
||||
// settings = {
|
||||
|
@ -18,6 +22,15 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
|||
// }
|
||||
//)
|
||||
public class DeleteUnloadedProxyTest {
|
||||
|
||||
@AfterEach
|
||||
public void cleanup(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.createMutationQuery( "delete from ParentSub" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from Child" ).executeUpdate();
|
||||
session.createMutationQuery( "delete from Parent" ).executeUpdate();
|
||||
} );
|
||||
}
|
||||
@Test
|
||||
public void testAttached(SessionFactoryScope scope) {
|
||||
Parent p = new Parent();
|
||||
|
@ -86,4 +99,48 @@ public class DeleteUnloadedProxyTest {
|
|||
assertNull( em.find( Child.class, c.getId() ) );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey( "HHH-16690" )
|
||||
public void testRePersist(SessionFactoryScope scope) {
|
||||
Parent p = new Parent();
|
||||
ParentSub ps = new ParentSub( 1L, "abc", p );
|
||||
scope.inTransaction( em -> {
|
||||
em.persist( p );
|
||||
em.persist( ps );
|
||||
} );
|
||||
scope.inTransaction( em -> {
|
||||
ParentSub sub = em.getReference( ParentSub.class, 1L );
|
||||
assertFalse( isInitialized( sub ) );
|
||||
em.remove( sub );
|
||||
em.persist( new ParentSub( 1L, "def", p ) );
|
||||
} );
|
||||
scope.inSession( em -> {
|
||||
ParentSub sub = em.find( ParentSub.class, 1L );
|
||||
assertNotNull( sub );
|
||||
assertEquals( "def", sub.getData() );
|
||||
} );
|
||||
}
|
||||
|
||||
@Test
|
||||
@JiraKey( "HHH-16690" )
|
||||
public void testReMerge(SessionFactoryScope scope) {
|
||||
Parent p = new Parent();
|
||||
ParentSub ps = new ParentSub( 1L, "abc", p );
|
||||
scope.inTransaction( em -> {
|
||||
em.persist( p );
|
||||
em.persist( ps );
|
||||
} );
|
||||
scope.inTransaction( em -> {
|
||||
ParentSub sub = em.getReference( ParentSub.class, 1L );
|
||||
assertFalse( isInitialized( sub ) );
|
||||
em.remove( sub );
|
||||
em.merge( new ParentSub( 1L, "def", p ) );
|
||||
} );
|
||||
scope.inSession( em -> {
|
||||
ParentSub sub = em.find( ParentSub.class, 1L );
|
||||
assertNotNull( sub );
|
||||
assertEquals( "def", sub.getData() );
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package org.hibernate.orm.test.deleteunloaded;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.FetchType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.OneToOne;
|
||||
|
||||
@Entity
|
||||
public class ParentSub {
|
||||
@Id
|
||||
private long id;
|
||||
private String data;
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
private Parent parent;
|
||||
|
||||
public ParentSub() {
|
||||
}
|
||||
|
||||
public ParentSub(long id, String data, Parent parent) {
|
||||
this.id = id;
|
||||
this.data = data;
|
||||
this.parent = parent;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void setData(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public Parent getParent() {
|
||||
return parent;
|
||||
}
|
||||
|
||||
public void setParent(Parent parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue