HHH-5983 : Actions have non-transient references that cause inconsistencies after deserialization
This commit is contained in:
parent
e1c03f28fd
commit
b1036c09a7
|
@ -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 );
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() ) );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
Loading…
Reference in New Issue