HHH-5124 - Removing an entity and persisting it again fails
This commit is contained in:
parent
ab09c575e6
commit
343269b00d
|
@ -75,7 +75,7 @@ public class ActionQueue {
|
|||
// they must happen in the right order so as to respect referential
|
||||
// integrity
|
||||
private ArrayList insertions;
|
||||
private ArrayList deletions;
|
||||
private ArrayList<EntityDeleteAction> deletions;
|
||||
private ArrayList updates;
|
||||
// Actually the semantics of the next three are really "Bag"
|
||||
// Note that, unlike objects, collection insertions, updates,
|
||||
|
@ -100,7 +100,7 @@ public class ActionQueue {
|
|||
|
||||
private void init() {
|
||||
insertions = new ArrayList( INIT_QUEUE_LIST_SIZE );
|
||||
deletions = new ArrayList( INIT_QUEUE_LIST_SIZE );
|
||||
deletions = new ArrayList<EntityDeleteAction>( INIT_QUEUE_LIST_SIZE );
|
||||
updates = new ArrayList( INIT_QUEUE_LIST_SIZE );
|
||||
|
||||
collectionCreations = new ArrayList( INIT_QUEUE_LIST_SIZE );
|
||||
|
@ -260,9 +260,8 @@ public class ActionQueue {
|
|||
}
|
||||
|
||||
private void executeActions(List list) throws HibernateException {
|
||||
int size = list.size();
|
||||
for ( int i = 0; i < size; i++ ) {
|
||||
execute( (Executable) list.get( i ) );
|
||||
for ( Object aList : list ) {
|
||||
execute( (Executable) aList );
|
||||
}
|
||||
list.clear();
|
||||
session.getTransactionCoordinator().getJdbcCoordinator().executeBatch();
|
||||
|
@ -406,6 +405,17 @@ public class ActionQueue {
|
|||
collectionCreations.size() > 0;
|
||||
}
|
||||
|
||||
public void unScheduleDeletion(EntityEntry entry, Object rescuedEntity) {
|
||||
for ( int i = 0; i < deletions.size(); i++ ) {
|
||||
EntityDeleteAction action = deletions.get( i );
|
||||
if ( action.getInstance() == rescuedEntity ) {
|
||||
deletions.remove( i );
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new AssertionFailure( "Unable to perform un-delete for instance " + entry.getEntityName() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by the owning session to explicitly control serialization of the
|
||||
* action queue
|
||||
|
@ -490,9 +500,9 @@ public class ActionQueue {
|
|||
|
||||
queueSize = ois.readInt();
|
||||
LOG.tracev( "Starting deserialization of [{0}] deletions entries", queueSize );
|
||||
rtn.deletions = new ArrayList<Executable>( queueSize );
|
||||
rtn.deletions = new ArrayList<EntityDeleteAction>( queueSize );
|
||||
for ( int i = 0; i < queueSize; i++ ) {
|
||||
EntityAction action = ( EntityAction ) ois.readObject();
|
||||
EntityDeleteAction action = ( EntityDeleteAction ) ois.readObject();
|
||||
action.afterDeserialize( session );
|
||||
rtn.deletions.add( action );
|
||||
}
|
||||
|
@ -551,16 +561,14 @@ public class ActionQueue {
|
|||
}
|
||||
|
||||
public void beforeTransactionCompletion() {
|
||||
final int size = processes.size();
|
||||
for ( int i = 0; i < size; i++ ) {
|
||||
for ( BeforeTransactionCompletionProcess process : processes ) {
|
||||
try {
|
||||
BeforeTransactionCompletionProcess process = processes.get( i );
|
||||
process.doBeforeTransactionCompletion( session );
|
||||
}
|
||||
catch ( HibernateException he ) {
|
||||
catch (HibernateException he) {
|
||||
throw he;
|
||||
}
|
||||
catch ( Exception e ) {
|
||||
catch (Exception e) {
|
||||
throw new AssertionFailure( "Unable to perform beforeTransactionCompletion callback", e );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,13 +34,14 @@ import org.hibernate.PersistentObjectException;
|
|||
import org.hibernate.engine.spi.CascadingAction;
|
||||
import org.hibernate.engine.spi.EntityEntry;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.engine.spi.Status;
|
||||
import org.hibernate.event.spi.EventSource;
|
||||
import org.hibernate.event.spi.PersistEvent;
|
||||
import org.hibernate.event.spi.PersistEventListener;
|
||||
import org.hibernate.id.ForeignGenerator;
|
||||
import org.hibernate.internal.CoreMessageLogger;
|
||||
import org.hibernate.internal.util.collections.IdentityMap;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.pretty.MessageHelper;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.hibernate.proxy.LazyInitializer;
|
||||
|
||||
|
@ -52,8 +53,20 @@ import org.hibernate.proxy.LazyInitializer;
|
|||
*/
|
||||
public class DefaultPersistEventListener extends AbstractSaveEventListener implements PersistEventListener {
|
||||
|
||||
private static final CoreMessageLogger LOG = Logger.getMessageLogger(CoreMessageLogger.class,
|
||||
DefaultPersistEventListener.class.getName());
|
||||
private static final CoreMessageLogger LOG = Logger.getMessageLogger(
|
||||
CoreMessageLogger.class,
|
||||
DefaultPersistEventListener.class.getName()
|
||||
);
|
||||
|
||||
@Override
|
||||
protected CascadingAction getCascadeAction() {
|
||||
return CascadingAction.PERSIST;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Boolean getAssumedUnsaved() {
|
||||
return Boolean.TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given create event.
|
||||
|
@ -65,7 +78,6 @@ public class DefaultPersistEventListener extends AbstractSaveEventListener imple
|
|||
onPersist( event, new IdentityHashMap(10) );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the given create event.
|
||||
*
|
||||
|
@ -125,27 +137,39 @@ public class DefaultPersistEventListener extends AbstractSaveEventListener imple
|
|||
}
|
||||
|
||||
switch ( entityState ) {
|
||||
case DETACHED:
|
||||
case DETACHED: {
|
||||
throw new PersistentObjectException(
|
||||
"detached entity passed to persist: " +
|
||||
getLoggableName( event.getEntityName(), entity )
|
||||
);
|
||||
case PERSISTENT:
|
||||
}
|
||||
case PERSISTENT: {
|
||||
entityIsPersistent( event, createCache );
|
||||
break;
|
||||
case TRANSIENT:
|
||||
}
|
||||
case TRANSIENT: {
|
||||
entityIsTransient( event, createCache );
|
||||
break;
|
||||
default:
|
||||
}
|
||||
case DELETED: {
|
||||
entityEntry.setStatus( Status.MANAGED );
|
||||
entityEntry.setDeletedState( null );
|
||||
event.getSession().getActionQueue().unScheduleDeletion( entityEntry, event.getObject() );
|
||||
entityIsDeleted( event, createCache );
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new ObjectDeletedException(
|
||||
"deleted entity passed to persist",
|
||||
null,
|
||||
getLoggableName( event.getEntityName(), entity )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"unchecked"})
|
||||
protected void entityIsPersistent(PersistEvent event, Map createCache) {
|
||||
LOG.trace( "Ignoring persistent instance" );
|
||||
final EventSource source = event.getSession();
|
||||
|
@ -156,40 +180,53 @@ public class DefaultPersistEventListener extends AbstractSaveEventListener imple
|
|||
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
||||
|
||||
if ( createCache.put(entity, entity)==null ) {
|
||||
//TODO: merge into one method!
|
||||
cascadeBeforeSave(source, persister, entity, createCache);
|
||||
cascadeAfterSave(source, persister, entity, createCache);
|
||||
}
|
||||
justCascade( createCache, source, entity, persister );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void justCascade(Map createCache, EventSource source, Object entity, EntityPersister persister) {
|
||||
//TODO: merge into one method!
|
||||
cascadeBeforeSave(source, persister, entity, createCache);
|
||||
cascadeAfterSave(source, persister, entity, createCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the given create event.
|
||||
*
|
||||
* @param event The save event to be handled.
|
||||
* @throws HibernateException
|
||||
* @param createCache The copy cache of entity instance to merge/copy instance.
|
||||
*/
|
||||
protected void entityIsTransient(PersistEvent event, Map createCache) throws HibernateException {
|
||||
|
||||
@SuppressWarnings( {"unchecked"})
|
||||
protected void entityIsTransient(PersistEvent event, Map createCache) {
|
||||
LOG.trace( "Saving transient instance" );
|
||||
|
||||
final EventSource source = event.getSession();
|
||||
|
||||
final Object entity = source.getPersistenceContext().unproxy( event.getObject() );
|
||||
|
||||
if ( createCache.put(entity, entity)==null ) {
|
||||
if ( createCache.put( entity, entity ) == null ) {
|
||||
saveWithGeneratedId( entity, event.getEntityName(), createCache, source, false );
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CascadingAction getCascadeAction() {
|
||||
return CascadingAction.PERSIST;
|
||||
}
|
||||
@SuppressWarnings( {"unchecked"})
|
||||
private void entityIsDeleted(PersistEvent event, Map createCache) {
|
||||
final EventSource source = event.getSession();
|
||||
|
||||
@Override
|
||||
protected Boolean getAssumedUnsaved() {
|
||||
return Boolean.TRUE;
|
||||
final Object entity = source.getPersistenceContext().unproxy( event.getObject() );
|
||||
final EntityPersister persister = source.getEntityPersister( event.getEntityName(), entity );
|
||||
|
||||
LOG.tracef(
|
||||
"un-scheduling entity deletion [%s]",
|
||||
MessageHelper.infoString(
|
||||
persister,
|
||||
persister.getIdentifier( entity, source ),
|
||||
source.getFactory()
|
||||
)
|
||||
);
|
||||
|
||||
if ( createCache.put( entity, entity ) == null ) {
|
||||
justCascade( createCache, source, entity, persister );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,14 +23,20 @@
|
|||
*/
|
||||
package org.hibernate.test.jpa.removed;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.test.jpa.AbstractJPATest;
|
||||
import org.hibernate.test.jpa.Item;
|
||||
import org.hibernate.test.jpa.Part;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -77,4 +83,117 @@ public class RemovedEntityTest extends AbstractJPATest {
|
|||
|
||||
assertNull( "expecting removed entity to be returned as null from get()", item );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveThenSave() {
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
Item item = new Item();
|
||||
item.setName( "dummy" );
|
||||
s.persist( item );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
Long id = item.getId();
|
||||
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
item = ( Item ) s.get( Item.class, id );
|
||||
String sessionAsString = s.toString();
|
||||
|
||||
s.delete( item );
|
||||
|
||||
Item item2 = ( Item ) s.get( Item.class, id );
|
||||
assertNull( "expecting removed entity to be returned as null from get()", item2 );
|
||||
|
||||
s.persist( item );
|
||||
assertEquals( "expecting session to be as it was before", sessionAsString, s.toString() );
|
||||
|
||||
item.setName("Rescued");
|
||||
item = ( Item ) s.get( Item.class, id );
|
||||
assertNotNull( "expecting rescued entity to be returned from get()", item );
|
||||
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
item = ( Item ) s.get( Item.class, id );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
assertNotNull( "expecting removed entity to be returned as null from get()", item );
|
||||
assertEquals("Rescued", item.getName());
|
||||
|
||||
// clean up
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
s.delete( item );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveThenSaveWithCascades() {
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
|
||||
Item item = new Item();
|
||||
item.setName( "dummy" );
|
||||
Part part = new Part(item, "child", "1234", BigDecimal.ONE);
|
||||
|
||||
// persist cascades to part
|
||||
s.persist( item );
|
||||
|
||||
// delete cascades to part also
|
||||
s.delete( item );
|
||||
assertFalse( "the item is contained in the session after deletion", s.contains( item ) );
|
||||
assertFalse( "the part is contained in the session after deletion", s.contains( part ) );
|
||||
|
||||
// now try to persist again as a "unschedule removal" operation
|
||||
s.persist( item );
|
||||
assertTrue( "the item is contained in the session after deletion", s.contains( item ) );
|
||||
assertTrue( "the part is contained in the session after deletion", s.contains( part ) );
|
||||
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
// clean up
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
s.delete( item );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemoveChildThenFlushWithCascadePersist() {
|
||||
Session s = openSession();
|
||||
s.beginTransaction();
|
||||
|
||||
Item item = new Item();
|
||||
item.setName( "dummy" );
|
||||
Part child = new Part(item, "child", "1234", BigDecimal.ONE);
|
||||
|
||||
// persist cascades to part
|
||||
s.persist( item );
|
||||
|
||||
// delete the part
|
||||
s.delete( child );
|
||||
assertFalse("the child is contained in the session, since it is deleted", s.contains(child) );
|
||||
|
||||
// now try to flush, which will attempt to cascade persist again to child.
|
||||
s.flush();
|
||||
assertTrue("Now it is consistent again since if was cascade-persisted by the flush()", s.contains(child));
|
||||
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
// clean up
|
||||
s = openSession();
|
||||
s.beginTransaction();
|
||||
s.delete( item );
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue