HHH-10708 - Accessing a lazy collection in an enhanced class deletes it afterwards

(cherry picked from commit 0e1b79d2b5)

Conflicts:
	hibernate-core/src/main/java/org/hibernate/engine/internal/Collections.java

HHH-10708 : Corrections due to backporting
This commit is contained in:
Steve Ebersole 2016-05-20 08:49:21 -05:00 committed by Gail Badner
parent 9768cf21c5
commit 0f918b4d42
6 changed files with 116 additions and 52 deletions

View File

@ -16,6 +16,7 @@ import org.hibernate.EntityMode;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityEntryExtraState;
@ -320,6 +321,15 @@ public abstract class AbstractEntityEntry implements Serializable, EntityEntry {
}
}
@Override
public void overwriteLoadedStateCollectionValue(String propertyName, PersistentCollection collection) {
assert propertyName != null;
assert loadedState != null;
final int propertyIndex = ( (UniqueKeyLoadable) persister ).getPropertyIndex( propertyName );
loadedState[propertyIndex] = collection;
}
@Override
public boolean requiresDirtyCheck(Object entity) {
return isModifiableEntity()

View File

@ -73,7 +73,7 @@ public final class Collections {
// the owning entity may have been deleted and its identifier unset due to
// identifier-rollback; in which case, try to look up its identifier from
// the persistence context
if ( session.getFactory().getSettings().isIdentifierRollbackEnabled() ) {
if ( session.getFactory().getSessionFactoryOptions().isIdentifierRollbackEnabled() ) {
final EntityEntry ownerEntry = persistenceContext.getEntry( coll.getOwner() );
if ( ownerEntry != null ) {
ownerId = ownerEntry.getId();
@ -156,41 +156,74 @@ public final class Collections {
);
}
final SessionFactoryImplementor factory = session.getFactory();
final CollectionPersister persister = factory.getCollectionPersister( type.getRole() );
ce.setCurrentPersister( persister );
//TODO: better to pass the id in as an argument?
ce.setCurrentKey( type.getKeyOfOwner( entity, session ) );
final boolean isBytecodeEnhanced = persister.getOwnerEntityPersister().getInstrumentationMetadata().isEnhancedForLazyLoading();
if ( isBytecodeEnhanced && !collection.wasInitialized() ) {
// skip it
LOG.debugf(
"Skipping uninitialized bytecode-lazy collection: %s",
MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session )
);
ce.setReached( true );
ce.setProcessed( true );
}
else {
// The CollectionEntry.isReached() stuff is just to detect any silly users
// who set up circular or shared references between/to collections.
if ( ce.isReached() ) {
// We've been here before
// We've been here beforeQuery
throw new HibernateException(
"Found shared references to a collection: " + type.getRole()
);
}
ce.setReached( true );
final SessionFactoryImplementor factory = session.getFactory();
final CollectionPersister persister = factory.getCollectionPersister( type.getRole() );
ce.setCurrentPersister( persister );
//TODO: better to pass the id in as an argument?
ce.setCurrentKey( type.getKeyOfOwner( entity, session ) );
if ( LOG.isDebugEnabled() ) {
if ( collection.wasInitialized() ) {
LOG.debugf(
"Collection found: %s, was: %s (initialized)",
MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ),
MessageHelper.collectionInfoString( ce.getLoadedPersister(), collection, ce.getLoadedKey(), session )
MessageHelper.collectionInfoString(
persister,
collection,
ce.getCurrentKey(),
session
),
MessageHelper.collectionInfoString(
ce.getLoadedPersister(),
collection,
ce.getLoadedKey(),
session
)
);
}
else {
LOG.debugf(
"Collection found: %s, was: %s (uninitialized)",
MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ),
MessageHelper.collectionInfoString( ce.getLoadedPersister(), collection, ce.getLoadedKey(), session )
MessageHelper.collectionInfoString(
persister,
collection,
ce.getCurrentKey(),
session
),
MessageHelper.collectionInfoString(
ce.getLoadedPersister(),
collection,
ce.getLoadedKey(),
session
)
);
}
}
prepareCollectionForUpdate( collection, ce, factory );
}
}
/**
* 1. record the collection role that this collection is referenced by

View File

@ -169,9 +169,9 @@ public final class CollectionEntry implements Serializable {
loadedKey = collection.getKey();
}
boolean nonMutableChange = collection.isDirty() &&
getLoadedPersister()!=null &&
!getLoadedPersister().isMutable();
boolean nonMutableChange = collection.isDirty()
&& getLoadedPersister() != null
&& !getLoadedPersister().isMutable();
if ( nonMutableChange ) {
throw new HibernateException(
"changed an immutable collection instance: " +
@ -182,24 +182,30 @@ public final class CollectionEntry implements Serializable {
dirty( collection );
if ( LOG.isDebugEnabled() && collection.isDirty() && getLoadedPersister() != null ) {
LOG.debugf( "Collection dirty: %s",
MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() ) );
LOG.debugf(
"Collection dirty: %s",
MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
);
}
setReached( false );
setProcessed( false );
setDoupdate( false );
setDoremove( false );
setDorecreate( false );
setReached(false);
setProcessed(false);
}
public void postInitialize(PersistentCollection collection) throws HibernateException {
snapshot = getLoadedPersister().isMutable() ?
collection.getSnapshot( getLoadedPersister() ) :
null;
snapshot = getLoadedPersister().isMutable()
? collection.getSnapshot( getLoadedPersister() )
: null;
collection.setSnapshot(loadedKey, role, snapshot);
if ( getLoadedPersister().getBatchSize() > 1 ) {
((AbstractPersistentCollection) collection).getSession().getPersistenceContext().getBatchFetchQueue().removeBatchLoadableCollection(this);
( (AbstractPersistentCollection) collection ).getSession()
.getPersistenceContext()
.getBatchFetchQueue()
.removeBatchLoadableCollection( this );
}
}

View File

@ -12,6 +12,7 @@ import java.io.Serializable;
import java.util.Set;
import org.hibernate.LockMode;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.persister.entity.EntityPersister;
/**
@ -38,6 +39,10 @@ public interface EntityEntry {
Object[] getLoadedState();
Object getLoadedValue(String propertyName);
void overwriteLoadedStateCollectionValue(String propertyName, PersistentCollection collection);
Object[] getDeletedState();
void setDeletedState(Object[] deletedState);
@ -87,8 +92,6 @@ public interface EntityEntry {
boolean isNullifiable(boolean earlyInsert, SessionImplementor session);
Object getLoadedValue(String propertyName);
/**
* Not sure this is the best method name, but the general idea here is to return {@code true} if the entity can
* possibly be dirty. This can only be the case if it is in a modifiable state (not read-only/deleted) and it

View File

@ -941,6 +941,17 @@ public abstract class AbstractEntityPersister
session.getPersistenceContext().addCollectionHolder( collection );
}
// update the "state" of the entity's EntityEntry to over-write UNFETCHED_PROPERTY reference
// for the collection to the just loaded collection
final EntityEntry ownerEntry = session.getPersistenceContext().getEntry( entity );
if ( ownerEntry == null ) {
// not good
throw new AssertionFailure(
"Could not locate EntityEntry for the collection owner in the PersistenceContext"
);
}
ownerEntry.overwriteLoadedStateCollectionValue( fieldName, collection );
// EARLY EXIT!!!
return collection;
}

View File

@ -63,7 +63,7 @@ public class UnexpectedDeleteOneTestTask extends AbstractEnhancerTestTask {
Foo foo = s.get( Foo.class, fooId );
// accessing the collection results in an exception
foo.bar.size();
foo.bars.size();
s.flush();
s.getTransaction().commit();
@ -88,8 +88,9 @@ public class UnexpectedDeleteOneTestTask extends AbstractEnhancerTestTask {
@Id @GeneratedValue
int id;
@OneToMany(orphanRemoval = true, mappedBy = Bar.FOO, targetEntity = Bar.class) @Cascade(CascadeType.ALL)
Set<Bar> bar = new HashSet<>();
@OneToMany(orphanRemoval = true, mappedBy = Bar.FOO, targetEntity = Bar.class)
@Cascade(CascadeType.ALL)
Set<Bar> bars = new HashSet<>();
}
}