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:
parent
9768cf21c5
commit
0f918b4d42
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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<>();
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue