HHH-5983 : Actions have non-transient references that cause inconsistencies after deserialization

This commit is contained in:
Gail Badner 2011-03-07 08:53:30 -08:00
parent e1c03f28fd
commit b1036c09a7
5 changed files with 90 additions and 29 deletions

View File

@ -44,11 +44,12 @@ import java.io.Serializable;
*/ */
public abstract class CollectionAction implements Executable, Serializable, Comparable { public abstract class CollectionAction implements Executable, Serializable, Comparable {
private transient CollectionPersister persister; private transient CollectionPersister persister;
private final Serializable key; private transient SessionImplementor session;
private final SessionImplementor session;
private final String collectionRole;
private final PersistentCollection collection; private final PersistentCollection collection;
private final Serializable key;
private final String collectionRole;
public CollectionAction( public CollectionAction(
final CollectionPersister persister, final CollectionPersister persister,
final PersistentCollection collection, final PersistentCollection collection,
@ -65,9 +66,19 @@ public abstract class CollectionAction implements Executable, Serializable, Comp
return collection; return collection;
} }
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { /**
ois.defaultReadObject(); * Reconnect to session after deserialization...
persister = session.getFactory().getCollectionPersister( collectionRole ); */
public void afterDeserialize(SessionImplementor session) {
if ( this.session != null || this.persister != null ) {
throw new IllegalStateException( "already attached to a session." );
}
// IMPL NOTE: non-flushed changes code calls this method with session == null...
// guard against NullPointerException
if ( session != null ) {
this.session = session;
this.persister = session.getFactory().getCollectionPersister( collectionRole );
}
} }
public final void beforeExecutions() throws CacheException { public final void beforeExecutions() throws CacheException {
@ -132,8 +143,8 @@ public abstract class CollectionAction implements Executable, Serializable, Comp
key, key,
persister.getKeyType(), persister.getKeyType(),
persister.getRole(), persister.getRole(),
session.getEntityMode(), session.getEntityMode(),
session.getFactory() session.getFactory()
); );
persister.getCacheAccessStrategy().remove( ck ); persister.getCacheAccessStrategy().remove( ck );
} }

View File

@ -25,6 +25,8 @@
package org.hibernate.action; package org.hibernate.action;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.SessionImplementor;
import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.entity.EntityPersister; import org.hibernate.persister.entity.EntityPersister;
@ -45,9 +47,9 @@ public abstract class EntityAction
private final String entityName; private final String entityName;
private final Serializable id; private final Serializable id;
private final Object instance;
private final SessionImplementor session;
private transient Object instance;
private transient SessionImplementor session;
private transient EntityPersister persister; private transient EntityPersister persister;
/** /**
@ -98,11 +100,18 @@ public abstract class EntityAction
*/ */
public final Serializable getId() { public final Serializable getId() {
if ( id instanceof DelayedPostInsertIdentifier ) { if ( id instanceof DelayedPostInsertIdentifier ) {
return session.getPersistenceContext().getEntry( instance ).getId(); Serializable eeId = session.getPersistenceContext().getEntry( instance ).getId();
return eeId instanceof DelayedPostInsertIdentifier ? null : eeId;
} }
return id; return id;
} }
public final DelayedPostInsertIdentifier getDelayedId() {
return DelayedPostInsertIdentifier.class.isInstance( id ) ?
DelayedPostInsertIdentifier.class.cast( id ) :
null;
}
/** /**
* entity instance accessor * entity instance accessor
* *
@ -156,15 +165,19 @@ public abstract class EntityAction
} }
/** /**
* Serialization... * Reconnect to session after deserialization...
*
* @param ois Thed object stream
* @throws IOException Problem performing the default stream reading
* @throws ClassNotFoundException Problem performing the default stream reading
*/ */
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException { public void afterDeserialize(SessionImplementor session) {
ois.defaultReadObject(); if ( this.session != null || this.persister != null ) {
persister = session.getFactory().getEntityPersister( entityName ); throw new IllegalStateException( "already attached to a session." );
}
// IMPL NOTE: non-flushed changes code calls this method with session == null...
// guard against NullPointerException
if ( session != null ) {
this.session = session;
this.persister = session.getFactory().getEntityPersister( entityName );
this.instance = session.getPersistenceContext().getEntity( new EntityKey( id, persister, session.getEntityMode() ) );
}
} }
} }

View File

@ -28,6 +28,7 @@ import java.io.Serializable;
import org.hibernate.HibernateException; import org.hibernate.HibernateException;
import org.hibernate.AssertionFailure; import org.hibernate.AssertionFailure;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.SessionImplementor; import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.EntityKey; import org.hibernate.engine.EntityKey;
import org.hibernate.event.PostInsertEvent; import org.hibernate.event.PostInsertEvent;
@ -39,7 +40,7 @@ import org.hibernate.persister.entity.EntityPersister;
public final class EntityIdentityInsertAction extends EntityAction { public final class EntityIdentityInsertAction extends EntityAction {
private final Object[] state; private transient Object[] state;
private final boolean isDelayed; private final boolean isDelayed;
private final EntityKey delayedEntityKey; private final EntityKey delayedEntityKey;
//private CacheEntry cacheEntry; //private CacheEntry cacheEntry;
@ -51,7 +52,12 @@ public final class EntityIdentityInsertAction extends EntityAction {
EntityPersister persister, EntityPersister persister,
SessionImplementor session, SessionImplementor session,
boolean isDelayed) throws HibernateException { boolean isDelayed) throws HibernateException {
super( session, null, instance, persister ); super(
session,
( isDelayed ? generateDelayedPostInsertIdentifier() : null ),
instance,
persister
);
this.state = state; this.state = state;
this.isDelayed = isDelayed; this.isDelayed = isDelayed;
this.delayedEntityKey = isDelayed ? generateDelayedEntityKey() : null; this.delayedEntityKey = isDelayed ? generateDelayedEntityKey() : null;
@ -171,10 +177,24 @@ public final class EntityIdentityInsertAction extends EntityAction {
return delayedEntityKey; return delayedEntityKey;
} }
private synchronized EntityKey generateDelayedEntityKey() { private synchronized static DelayedPostInsertIdentifier generateDelayedPostInsertIdentifier() {
return new DelayedPostInsertIdentifier();
}
private EntityKey generateDelayedEntityKey() {
if ( !isDelayed ) { if ( !isDelayed ) {
throw new AssertionFailure( "cannot request delayed entity-key for non-delayed post-insert-id generation" ); throw new AssertionFailure( "cannot request delayed entity-key for non-delayed post-insert-id generation" );
} }
return new EntityKey( new DelayedPostInsertIdentifier(), getPersister(), getSession().getEntityMode() ); return new EntityKey( getDelayedId(), getPersister(), getSession().getEntityMode() );
}
public void afterDeserialize(SessionImplementor session) {
super.afterDeserialize( session );
// IMPL NOTE: non-flushed changes code calls this method with session == null...
// guard against NullPointerException
if ( session != null ) {
EntityEntry entityEntry = session.getPersistenceContext().getEntry( getInstance() );
this.state = entityEntry.getLoadedState();
}
} }
} }

View File

@ -42,9 +42,11 @@ import org.hibernate.HibernateException;
import org.hibernate.action.AfterTransactionCompletionProcess; import org.hibernate.action.AfterTransactionCompletionProcess;
import org.hibernate.action.BeforeTransactionCompletionProcess; import org.hibernate.action.BeforeTransactionCompletionProcess;
import org.hibernate.action.BulkOperationCleanupAction; import org.hibernate.action.BulkOperationCleanupAction;
import org.hibernate.action.CollectionAction;
import org.hibernate.action.CollectionRecreateAction; import org.hibernate.action.CollectionRecreateAction;
import org.hibernate.action.CollectionRemoveAction; import org.hibernate.action.CollectionRemoveAction;
import org.hibernate.action.CollectionUpdateAction; import org.hibernate.action.CollectionUpdateAction;
import org.hibernate.action.EntityAction;
import org.hibernate.action.EntityDeleteAction; import org.hibernate.action.EntityDeleteAction;
import org.hibernate.action.EntityIdentityInsertAction; import org.hibernate.action.EntityIdentityInsertAction;
import org.hibernate.action.EntityInsertAction; import org.hibernate.action.EntityInsertAction;
@ -480,42 +482,54 @@ public class ActionQueue {
log.trace( "starting deserialization of [" + queueSize + "] insertions entries" ); log.trace( "starting deserialization of [" + queueSize + "] insertions entries" );
rtn.insertions = new ArrayList<Executable>( queueSize ); rtn.insertions = new ArrayList<Executable>( queueSize );
for ( int i = 0; i < queueSize; i++ ) { for ( int i = 0; i < queueSize; i++ ) {
rtn.insertions.add( ois.readObject() ); EntityAction action = ( EntityAction ) ois.readObject();
action.afterDeserialize( session );
rtn.insertions.add( action );
} }
queueSize = ois.readInt(); queueSize = ois.readInt();
log.trace( "starting deserialization of [" + queueSize + "] deletions entries" ); log.trace( "starting deserialization of [" + queueSize + "] deletions entries" );
rtn.deletions = new ArrayList<Executable>( queueSize ); rtn.deletions = new ArrayList<Executable>( queueSize );
for ( int i = 0; i < queueSize; i++ ) { for ( int i = 0; i < queueSize; i++ ) {
rtn.deletions.add( ois.readObject() ); EntityAction action = ( EntityAction ) ois.readObject();
action.afterDeserialize( session );
rtn.deletions.add( action );
} }
queueSize = ois.readInt(); queueSize = ois.readInt();
log.trace( "starting deserialization of [" + queueSize + "] updates entries" ); log.trace( "starting deserialization of [" + queueSize + "] updates entries" );
rtn.updates = new ArrayList<Executable>( queueSize ); rtn.updates = new ArrayList<Executable>( queueSize );
for ( int i = 0; i < queueSize; i++ ) { for ( int i = 0; i < queueSize; i++ ) {
rtn.updates.add( ois.readObject() ); EntityAction action = ( EntityAction ) ois.readObject();
action.afterDeserialize( session );
rtn.updates.add( action );
} }
queueSize = ois.readInt(); queueSize = ois.readInt();
log.trace( "starting deserialization of [" + queueSize + "] collectionUpdates entries" ); log.trace( "starting deserialization of [" + queueSize + "] collectionUpdates entries" );
rtn.collectionUpdates = new ArrayList<Executable>( queueSize ); rtn.collectionUpdates = new ArrayList<Executable>( queueSize );
for ( int i = 0; i < queueSize; i++ ) { for ( int i = 0; i < queueSize; i++ ) {
rtn.collectionUpdates.add( ois.readObject() ); CollectionAction action = ( CollectionAction ) ois.readObject();
action.afterDeserialize( session );
rtn.collectionUpdates.add( action );
} }
queueSize = ois.readInt(); queueSize = ois.readInt();
log.trace( "starting deserialization of [" + queueSize + "] collectionRemovals entries" ); log.trace( "starting deserialization of [" + queueSize + "] collectionRemovals entries" );
rtn.collectionRemovals = new ArrayList<Executable>( queueSize ); rtn.collectionRemovals = new ArrayList<Executable>( queueSize );
for ( int i = 0; i < queueSize; i++ ) { for ( int i = 0; i < queueSize; i++ ) {
rtn.collectionRemovals.add( ois.readObject() ); CollectionAction action = ( CollectionAction ) ois.readObject();
action.afterDeserialize( session );
rtn.collectionRemovals.add( action );
} }
queueSize = ois.readInt(); queueSize = ois.readInt();
log.trace( "starting deserialization of [" + queueSize + "] collectionCreations entries" ); log.trace( "starting deserialization of [" + queueSize + "] collectionCreations entries" );
rtn.collectionCreations = new ArrayList<Executable>( queueSize ); rtn.collectionCreations = new ArrayList<Executable>( queueSize );
for ( int i = 0; i < queueSize; i++ ) { for ( int i = 0; i < queueSize; i++ ) {
rtn.collectionCreations.add( ois.readObject() ); CollectionAction action = ( CollectionAction ) ois.readObject();
action.afterDeserialize( session );
rtn.collectionCreations.add( action );
} }
return rtn; return rtn;
} }

View File

@ -6,6 +6,7 @@ import java.util.Collection;
import junit.framework.Test; import junit.framework.Test;
import org.hibernate.FlushMode;
import org.hibernate.Hibernate; import org.hibernate.Hibernate;
import org.hibernate.PersistentObjectException; import org.hibernate.PersistentObjectException;
import org.hibernate.Session; import org.hibernate.Session;
@ -104,6 +105,7 @@ public class CreateTest extends AbstractOperationTestCase {
s = applyNonFlushedChangesToNewSessionCloseOldSession( s ); s = applyNonFlushedChangesToNewSessionCloseOldSession( s );
s.persist( root ); s.persist( root );
s = applyNonFlushedChangesToNewSessionCloseOldSession( s ); s = applyNonFlushedChangesToNewSessionCloseOldSession( s );
root = ( NumberedNode ) getOldToNewEntityRefMap().get( root );
SimpleJtaTransactionManagerImpl.getInstance().commit(); SimpleJtaTransactionManagerImpl.getInstance().commit();
assertInsertCount( 2 ); assertInsertCount( 2 );
@ -178,6 +180,7 @@ public class CreateTest extends AbstractOperationTestCase {
dupe = ( NumberedNode ) getOldToNewEntityRefMap().get( dupe ); dupe = ( NumberedNode ) getOldToNewEntityRefMap().get( dupe );
s.persist( dupe ); s.persist( dupe );
s = applyNonFlushedChangesToNewSessionCloseOldSession( s ); s = applyNonFlushedChangesToNewSessionCloseOldSession( s );
dupe = ( NumberedNode ) getOldToNewEntityRefMap().get( dupe );
SimpleJtaTransactionManagerImpl.getInstance().commit(); SimpleJtaTransactionManagerImpl.getInstance().commit();
SimpleJtaTransactionManagerImpl.getInstance().begin(); SimpleJtaTransactionManagerImpl.getInstance().begin();