HHH-5124 - Removing an entity and persisting it again fails

This commit is contained in:
Steve Ebersole 2012-01-05 13:48:02 -06:00
parent ab09c575e6
commit 343269b00d
3 changed files with 201 additions and 37 deletions

View File

@ -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 );
}
}

View File

@ -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 );
}
}
}

View File

@ -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();
}
}