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

(cherry picked from commit 0f918b4d42)

Conflicts:
	hibernate-core/src/main/java/org/hibernate/engine/internal/AbstractEntityEntry.java
This commit is contained in:
Steve Ebersole 2016-05-20 08:49:21 -05:00 committed by Gail Badner
parent fffd2515a6
commit 33e16f2788
6 changed files with 116 additions and 52 deletions

View File

@ -17,6 +17,7 @@ import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.Session;
import org.hibernate.bytecode.instrumentation.spi.FieldInterceptor;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.spi.CachedNaturalIdValueSource;
import org.hibernate.engine.spi.EntityEntry;
import org.hibernate.engine.spi.EntityEntryExtraState;
@ -332,6 +333,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

@ -43,7 +43,7 @@ public final class Collections {
* @param session The session
*/
public static void processUnreachableCollection(PersistentCollection coll, SessionImplementor session) {
if ( coll.getOwner()==null ) {
if ( coll.getOwner() == null ) {
processNeverReferencedCollection( coll, session );
}
else {
@ -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,40 +156,73 @@ public final class Collections {
);
}
// 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
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 )
);
}
else {
LOG.debugf(
"Collection found: %s, was: %s (uninitialized)",
MessageHelper.collectionInfoString( persister, collection, ce.getCurrentKey(), session ),
MessageHelper.collectionInfoString( ce.getLoadedPersister(), collection, ce.getLoadedKey(), 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 beforeQuery
throw new HibernateException(
"Found shared references to a collection: " + type.getRole()
);
}
ce.setReached( true );
prepareCollectionForUpdate( collection, ce, factory );
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
)
);
}
else {
LOG.debugf(
"Collection found: %s, was: %s (uninitialized)",
MessageHelper.collectionInfoString(
persister,
collection,
ce.getCurrentKey(),
session
),
MessageHelper.collectionInfoString(
ce.getLoadedPersister(),
collection,
ce.getLoadedKey(),
session
)
);
}
}
prepareCollectionForUpdate( collection, ce, factory );
}
}
/**

View File

@ -169,37 +169,43 @@ public final class CollectionEntry implements Serializable {
loadedKey = collection.getKey();
}
boolean nonMutableChange = collection.isDirty() &&
getLoadedPersister()!=null &&
!getLoadedPersister().isMutable();
if (nonMutableChange) {
boolean nonMutableChange = collection.isDirty()
&& getLoadedPersister() != null
&& !getLoadedPersister().isMutable();
if ( nonMutableChange ) {
throw new HibernateException(
"changed an immutable collection instance: " +
MessageHelper.collectionInfoString( getLoadedPersister().getRole(), getLoadedKey() )
);
);
}
dirty(collection);
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() )
);
}
setDoupdate(false);
setDoremove(false);
setDorecreate(false);
setReached(false);
setProcessed(false);
setReached( false );
setProcessed( false );
setDoupdate( false );
setDoremove( false );
setDorecreate( 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);
if ( getLoadedPersister().getBatchSize() > 1 ) {
( (AbstractPersistentCollection) collection ).getSession()
.getPersistenceContext()
.getBatchFetchQueue()
.removeBatchLoadableCollection( this );
}
}
@ -273,7 +279,7 @@ public final class CollectionEntry implements Serializable {
}
void afterDeserialize(SessionFactoryImplementor factory) {
loadedPersister = ( factory == null ? null : factory.getCollectionPersister(role) );
loadedPersister = ( factory == null ? null : factory.getCollectionPersister( role ) );
}
public boolean wasDereferenced() {

View File

@ -11,6 +11,7 @@ import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.hibernate.LockMode;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.persister.entity.EntityPersister;
/**
@ -37,6 +38,10 @@ public interface EntityEntry {
Object[] getLoadedState();
Object getLoadedValue(String propertyName);
void overwriteLoadedStateCollectionValue(String propertyName, PersistentCollection collection);
Object[] getDeletedState();
void setDeletedState(Object[] deletedState);
@ -86,8 +91,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

@ -928,6 +928,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<>();
}
}