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 {
private transient CollectionPersister persister;
private final Serializable key;
private final SessionImplementor session;
private final String collectionRole;
private transient SessionImplementor session;
private final PersistentCollection collection;
private final Serializable key;
private final String collectionRole;
public CollectionAction(
final CollectionPersister persister,
final PersistentCollection collection,
@ -65,9 +66,19 @@ public abstract class CollectionAction implements Executable, Serializable, Comp
return collection;
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
persister = session.getFactory().getCollectionPersister( collectionRole );
/**
* Reconnect to session after deserialization...
*/
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 {
@ -132,8 +143,8 @@ public abstract class CollectionAction implements Executable, Serializable, Comp
key,
persister.getKeyType(),
persister.getRole(),
session.getEntityMode(),
session.getFactory()
session.getEntityMode(),
session.getFactory()
);
persister.getCacheAccessStrategy().remove( ck );
}

View File

@ -25,6 +25,8 @@
package org.hibernate.action;
import org.hibernate.AssertionFailure;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.EntityKey;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.internal.util.StringHelper;
import org.hibernate.persister.entity.EntityPersister;
@ -45,9 +47,9 @@ public abstract class EntityAction
private final String entityName;
private final Serializable id;
private final Object instance;
private final SessionImplementor session;
private transient Object instance;
private transient SessionImplementor session;
private transient EntityPersister persister;
/**
@ -98,11 +100,18 @@ public abstract class EntityAction
*/
public final Serializable getId() {
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;
}
public final DelayedPostInsertIdentifier getDelayedId() {
return DelayedPostInsertIdentifier.class.isInstance( id ) ?
DelayedPostInsertIdentifier.class.cast( id ) :
null;
}
/**
* entity instance accessor
*
@ -156,15 +165,19 @@ public abstract class EntityAction
}
/**
* Serialization...
*
* @param ois Thed object stream
* @throws IOException Problem performing the default stream reading
* @throws ClassNotFoundException Problem performing the default stream reading
* Reconnect to session after deserialization...
*/
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
persister = session.getFactory().getEntityPersister( entityName );
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().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.AssertionFailure;
import org.hibernate.engine.EntityEntry;
import org.hibernate.engine.SessionImplementor;
import org.hibernate.engine.EntityKey;
import org.hibernate.event.PostInsertEvent;
@ -39,7 +40,7 @@ import org.hibernate.persister.entity.EntityPersister;
public final class EntityIdentityInsertAction extends EntityAction {
private final Object[] state;
private transient Object[] state;
private final boolean isDelayed;
private final EntityKey delayedEntityKey;
//private CacheEntry cacheEntry;
@ -51,7 +52,12 @@ public final class EntityIdentityInsertAction extends EntityAction {
EntityPersister persister,
SessionImplementor session,
boolean isDelayed) throws HibernateException {
super( session, null, instance, persister );
super(
session,
( isDelayed ? generateDelayedPostInsertIdentifier() : null ),
instance,
persister
);
this.state = state;
this.isDelayed = isDelayed;
this.delayedEntityKey = isDelayed ? generateDelayedEntityKey() : null;
@ -171,10 +177,24 @@ public final class EntityIdentityInsertAction extends EntityAction {
return delayedEntityKey;
}
private synchronized EntityKey generateDelayedEntityKey() {
private synchronized static DelayedPostInsertIdentifier generateDelayedPostInsertIdentifier() {
return new DelayedPostInsertIdentifier();
}
private EntityKey generateDelayedEntityKey() {
if ( !isDelayed ) {
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.BeforeTransactionCompletionProcess;
import org.hibernate.action.BulkOperationCleanupAction;
import org.hibernate.action.CollectionAction;
import org.hibernate.action.CollectionRecreateAction;
import org.hibernate.action.CollectionRemoveAction;
import org.hibernate.action.CollectionUpdateAction;
import org.hibernate.action.EntityAction;
import org.hibernate.action.EntityDeleteAction;
import org.hibernate.action.EntityIdentityInsertAction;
import org.hibernate.action.EntityInsertAction;
@ -480,42 +482,54 @@ public class ActionQueue {
log.trace( "starting deserialization of [" + queueSize + "] insertions entries" );
rtn.insertions = new ArrayList<Executable>( queueSize );
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();
log.trace( "starting deserialization of [" + queueSize + "] deletions entries" );
rtn.deletions = new ArrayList<Executable>( queueSize );
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();
log.trace( "starting deserialization of [" + queueSize + "] updates entries" );
rtn.updates = new ArrayList<Executable>( queueSize );
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();
log.trace( "starting deserialization of [" + queueSize + "] collectionUpdates entries" );
rtn.collectionUpdates = new ArrayList<Executable>( queueSize );
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();
log.trace( "starting deserialization of [" + queueSize + "] collectionRemovals entries" );
rtn.collectionRemovals = new ArrayList<Executable>( queueSize );
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();
log.trace( "starting deserialization of [" + queueSize + "] collectionCreations entries" );
rtn.collectionCreations = new ArrayList<Executable>( queueSize );
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;
}

View File

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