clean up CollectionType

This commit is contained in:
Gavin King 2024-11-17 11:30:25 +01:00
parent 8f85e6ca47
commit 2c08833c6d
1 changed files with 257 additions and 236 deletions

View File

@ -10,8 +10,6 @@ import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
@ -34,13 +32,11 @@ import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.MarkerObject;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.internal.util.collections.CollectionHelper;
import org.hibernate.metamodel.CollectionClassification;
import org.hibernate.metamodel.mapping.PluralAttributeMapping;
import org.hibernate.persister.collection.CollectionPersister;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.proxy.LazyInitializer;
import org.hibernate.sql.results.graph.collection.LoadingCollectionEntry;
@ -49,6 +45,10 @@ import org.jboss.logging.Logger;
import org.checkerframework.checker.nullness.qual.Nullable;
import static org.hibernate.bytecode.enhance.spi.LazyPropertyInitializer.UNFETCHED_PROPERTY;
import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_BOOLEAN_ARRAY;
import static org.hibernate.internal.util.collections.ArrayHelper.EMPTY_INT_ARRAY;
import static org.hibernate.internal.util.collections.CollectionHelper.mapOfSize;
import static org.hibernate.pretty.MessageHelper.collectionInfoString;
import static org.hibernate.proxy.HibernateProxy.extractLazyInitializer;
/**
@ -90,14 +90,13 @@ public abstract class CollectionType extends AbstractType implements Association
// collections, since they can only occur for inverse collections!
final Iterator<?> elems = getElementsIterator( collection );
while ( elems.hasNext() ) {
Object element = elems.next();
final Object maybeProxy = elems.next();
// worrying about proxies is perhaps a little bit of overkill here...
final LazyInitializer li = extractLazyInitializer( element );
if ( li != null ) {
if ( !li.isUninitialized() ) {
element = li.getImplementation();
}
}
final LazyInitializer initializer = extractLazyInitializer( maybeProxy );
final Object element =
initializer != null && !initializer.isUninitialized()
? initializer.getImplementation()
: maybeProxy;
if ( element == childObject ) {
return true;
}
@ -113,13 +112,13 @@ public abstract class CollectionType extends AbstractType implements Association
@Override
public final boolean isEqual(Object x, Object y) {
return x == y
|| x instanceof PersistentCollection && isEqual( (PersistentCollection<?>) x, y )
|| y instanceof PersistentCollection && isEqual( (PersistentCollection<?>) y, x );
|| x instanceof PersistentCollection<?> xc && isEqual( xc, y )
|| y instanceof PersistentCollection<?> yc && isEqual( yc, x );
}
private boolean isEqual(PersistentCollection<?> x, Object y) {
return x.wasInitialized()
&& ( x.isWrapper( y ) || x.isDirectlyProvidedCollection( y ) );
private boolean isEqual(PersistentCollection<?> collection, Object other) {
return collection.wasInitialized()
&& ( collection.isWrapper( other ) || collection.isDirectlyProvidedCollection( other ) );
}
@Override
@ -161,7 +160,7 @@ public abstract class CollectionType extends AbstractType implements Association
@Override
public int[] getSqlTypeCodes(MappingContext mappingContext) throws MappingException {
return ArrayHelper.EMPTY_INT_ARRAY;
return EMPTY_INT_ARRAY;
}
@Override
@ -174,43 +173,43 @@ public abstract class CollectionType extends AbstractType implements Association
if ( value == null ) {
return "null";
}
if ( !getReturnedClass().isInstance( value ) && !(value instanceof PersistentCollection) ) {
// its most likely the collection-key
final CollectionPersister persister = getPersister( factory );
final Type keyType = persister.getKeyType();
if ( keyType.getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + keyType.toLoggableString( value, factory );
}
else {
// although it could also be the collection-id
else {
if ( !getReturnedClass().isInstance( value )
&& !(value instanceof PersistentCollection) ) {
final CollectionPersister persister = getPersister( factory );
final Type keyType = persister.getKeyType();
final Type identifierType = persister.getIdentifierType();
if ( identifierType != null
// its most likely the collection-key
if ( keyType.getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + keyType.toLoggableString( value, factory );
}
// although it could also be the collection-id
else if ( identifierType != null
&& identifierType.getReturnedClass().isInstance( value ) ) {
return getRole() + "#" + identifierType.toLoggableString( value, factory );
}
}
return renderLoggableString( value, factory );
}
return renderLoggableString( value, factory );
}
protected String renderLoggableString(Object value, SessionFactoryImplementor factory) {
if ( !Hibernate.isInitialized( value ) ) {
return "<uninitialized>";
}
final List<String> list = new ArrayList<>();
final Type elemType = getElementType( factory );
final Iterator<?> itr = getElementsIterator( value );
while ( itr.hasNext() ) {
final Object element = itr.next();
final String string =
element == UNFETCHED_PROPERTY || !Hibernate.isInitialized(element)
? "<uninitialized>"
: elemType.toLoggableString( element, factory );
list.add( string );
else {
final List<String> list = new ArrayList<>();
final Type elemType = getElementType( factory );
getElementsIterator( value )
.forEachRemaining( element -> list.add( elementString( factory, element, elemType ) ) );
return list.toString();
}
return list.toString();
}
private static String elementString(SessionFactoryImplementor factory, Object element, Type elemType) {
return element == UNFETCHED_PROPERTY || !Hibernate.isInitialized( element )
? "<uninitialized>"
: elemType.toLoggableString( element, factory );
}
@Override
@ -303,25 +302,24 @@ public abstract class CollectionType extends AbstractType implements Association
* @return The underlying collection persister
*/
private CollectionPersister getPersister(SharedSessionContractImplementor session) {
CollectionPersister p = this.persister;
return p != null ? p : getPersister( session.getFactory() );
return getPersister( session.getFactory() );
}
private CollectionPersister getPersister(SessionFactoryImplementor factory) {
CollectionPersister p = this.persister;
if ( p != null ) {
return p;
CollectionPersister persister = this.persister;
if ( persister != null ) {
return persister;
}
else {
synchronized ( this ) {
p = this.persister;
if ( p != null ) {
return p;
persister = this.persister;
if ( persister != null ) {
return persister;
}
else {
p = factory.getRuntimeMetamodels().getMappingMetamodel().getCollectionDescriptor( role );
this.persister = p;
return p;
persister = factory.getMappingMetamodel().getCollectionDescriptor( role );
this.persister = persister;
return persister;
}
}
}
@ -408,8 +406,7 @@ public abstract class CollectionType extends AbstractType implements Association
return key;
}
else {
final CollectionPersister persister = getPersister( session );
final EntityPersister ownerPersister = persister.getOwnerEntityPersister();
final EntityPersister ownerPersister = getPersister( session ).getOwnerEntityPersister();
// TODO: Fix this so it will work for non-POJO entity mode
final Class<?> keyClass = keyClass( session );
if ( ownerPersister.getMappedClass().isAssignableFrom( keyClass )
@ -452,7 +449,7 @@ public abstract class CollectionType extends AbstractType implements Association
@Override
public Joinable getAssociatedJoinable(SessionFactoryImplementor factory)
throws MappingException {
return (Joinable) factory.getRuntimeMetamodels().getMappingMetamodel().getCollectionDescriptor( role );
return (Joinable) factory.getMappingMetamodel().getCollectionDescriptor( role );
}
@Override
@ -463,22 +460,12 @@ public abstract class CollectionType extends AbstractType implements Association
@Override
public String getAssociatedEntityName(SessionFactoryImplementor factory)
throws MappingException {
try {
CollectionPersister collectionPersister = factory.getRuntimeMetamodels().getMappingMetamodel().getCollectionDescriptor( role );
if ( !( collectionPersister.getElementType() instanceof EntityType ) ) {
throw new MappingException(
"collection was not an association: " +
collectionPersister.getRole()
);
}
return collectionPersister.getElementPersister().getEntityName();
final CollectionPersister persister = factory.getMappingMetamodel().getCollectionDescriptor( role );
if ( persister.getElementType().isEntityType() ) {
return persister.getElementPersister().getEntityName();
}
catch (ClassCastException cce) {
throw new MappingException( "collection role is not queryable " + role );
else {
throw new MappingException( "Collection is not an association: " + persister.getRole() );
}
}
@ -499,13 +486,13 @@ public abstract class CollectionType extends AbstractType implements Association
Object owner,
Map<Object, Object> copyCache,
SharedSessionContractImplementor session) {
Collection result = (Collection) target;
final Collection result = (Collection) target;
result.clear();
// copy elements into newly empty target collection
Type elemType = getElementType( session.getFactory() );
for ( Object o : (Collection<?>) original ) {
result.add( elemType.replace(o, null, session, owner, copyCache) );
final Type elemType = getElementType( session.getFactory() );
for ( Object o : (Collection) original ) {
result.add( elemType.replace( o, null, session, owner, copyCache ) );
}
// if the original is a PersistentCollection, and that original
@ -517,7 +504,8 @@ public abstract class CollectionType extends AbstractType implements Association
// on the target because we simply do not know...
if ( original instanceof PersistentCollection<?> originalPersistentCollection
&& result instanceof PersistentCollection<?> resultPersistentCollection) {
preserveSnapshot( originalPersistentCollection, resultPersistentCollection, elemType, owner, copyCache, session );
preserveSnapshot( originalPersistentCollection, resultPersistentCollection,
elemType, owner, copyCache, session );
if ( !originalPersistentCollection.isDirty() ) {
resultPersistentCollection.clearDirty();
}
@ -533,71 +521,85 @@ public abstract class CollectionType extends AbstractType implements Association
Object owner,
Map<Object, Object> copyCache,
SharedSessionContractImplementor session) {
Serializable originalSnapshot = original.getStoredSnapshot();
Serializable resultSnapshot = result.getStoredSnapshot();
Serializable targetSnapshot;
if ( originalSnapshot instanceof List ) {
ArrayList<Object> targetList = new ArrayList<>( ( (List<?>) originalSnapshot ).size() );
targetSnapshot = targetList;
for ( Object obj : (List<?>) originalSnapshot ) {
targetList.add( elemType.replace( obj, null, session, owner, copyCache ) );
}
final CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result );
if ( ce != null ) {
ce.resetStoredSnapshot( result,
createSnapshot( original, result, elemType, owner, copyCache, session ) );
}
else if ( originalSnapshot instanceof Map ) {
Map<Object,Object> targetMap;
if ( originalSnapshot instanceof SortedMap ) {
@SuppressWarnings({"unchecked", "rawtypes"})
Comparator<Object> comparator = ((SortedMap) originalSnapshot).comparator();
targetMap = new TreeMap<>( comparator );
}
else {
targetMap = new HashMap<>(
CollectionHelper.determineProperSizing( ( (Map<?,?>) originalSnapshot ).size() ),
CollectionHelper.LOAD_FACTOR
);
}
targetSnapshot = (Serializable) targetMap;
for ( Map.Entry<?,?> entry : ( (Map<?, ?>) originalSnapshot ).entrySet() ) {
Object key = entry.getKey();
Object value = entry.getValue();
Object resultSnapshotValue = ( resultSnapshot == null )
? null
: ( (Map<?,?>) resultSnapshot ).get( key );
Object newValue = elemType.replace( value, resultSnapshotValue, session, owner, copyCache );
if ( key == value ) {
targetMap.put( newValue, newValue );
}
else {
targetMap.put( key, newValue );
}
}
}
private static Serializable createSnapshot(
PersistentCollection<?> original,
PersistentCollection<?> result,
Type elemType,
Object owner,
Map<Object, Object> copyCache,
SharedSessionContractImplementor session) {
final Serializable originalSnapshot = original.getStoredSnapshot();
if ( originalSnapshot instanceof List<?> list ) {
return createListSnapshot( list, elemType, owner, copyCache, session );
}
else if ( originalSnapshot instanceof Map<?,?> map ) {
return createMapSnapshot( map, result, elemType, owner, copyCache, session );
}
else if ( originalSnapshot instanceof Object[] array ) {
for ( int i = 0; i < array.length; i++ ) {
array[i] = elemType.replace( array[i], null, session, owner, copyCache );
}
targetSnapshot = originalSnapshot;
return createArraySnapshot( array, elemType, owner, copyCache, session );
}
else {
// retain the same snapshot
targetSnapshot = resultSnapshot;
return result.getStoredSnapshot();
}
}
CollectionEntry ce = session.getPersistenceContextInternal().getCollectionEntry( result );
if ( ce != null ) {
ce.resetStoredSnapshot( result, targetSnapshot );
private static Serializable createArraySnapshot(
Object[] array,
Type elemType,
Object owner,
Map<Object, Object> copyCache,
SharedSessionContractImplementor session) {
for ( int i = 0; i < array.length; i++ ) {
array[i] = elemType.replace( array[i], null, session, owner, copyCache );
}
return array;
}
private static Serializable createMapSnapshot(
Map<?, ?> map,
PersistentCollection<?> result,
Type elemType,
Object owner,
Map<Object, Object> copyCache,
SharedSessionContractImplementor session) {
final Map<?,?> resultSnapshot = (Map<?,?>) result.getStoredSnapshot();
final Map<Object, Object> targetMap;
if ( map instanceof SortedMap<?,?> sortedMap ) {
//noinspection unchecked, rawtypes
targetMap = new TreeMap( sortedMap.comparator() );
}
else {
targetMap = mapOfSize( map.size() );
}
for ( Map.Entry<?,?> entry : map.entrySet() ) {
final Object key = entry.getKey();
final Object value = entry.getValue();
final Object resultSnapshotValue = resultSnapshot == null ? null : resultSnapshot.get( key );
final Object newValue = elemType.replace( value, resultSnapshotValue, session, owner, copyCache );
targetMap.put( key == value ? newValue : key, newValue );
}
return (Serializable) targetMap;
}
private static ArrayList<Object> createListSnapshot(
List<?> list,
Type elemType,
Object owner,
Map<Object, Object> copyCache,
SharedSessionContractImplementor session) {
final ArrayList<Object> targetList = new ArrayList<>( list.size() );
for ( Object obj : list ) {
targetList.add( elemType.replace( obj, null, session, owner, copyCache ) );
}
return targetList;
}
/**
@ -632,86 +634,112 @@ public abstract class CollectionType extends AbstractType implements Association
final Object owner,
final Map<Object, Object> copyCache) throws HibernateException {
if ( original == null ) {
if ( target == null ) {
return null;
}
if ( target instanceof Collection<?> collection ) {
collection.clear();
return collection;
}
else if ( target instanceof Map<?,?> map ) {
map.clear();
return map;
}
else {
final PersistenceContext persistenceContext = session.getPersistenceContext();
final PersistentCollection<?> collectionHolder = persistenceContext
.getCollectionHolder( target );
if ( collectionHolder != null ) {
if ( collectionHolder instanceof PersistentArrayHolder<?> persistentArrayHolder ) {
persistenceContext.removeCollectionHolder( target );
persistentArrayHolder.beginRead();
persistentArrayHolder.injectLoadedState( persistenceContext.getCollectionEntry( collectionHolder ).getLoadedPersister().getAttributeMapping(), null );
persistentArrayHolder.endRead();
persistentArrayHolder.dirty();
persistenceContext.addCollectionHolder( collectionHolder );
return persistentArrayHolder.getArray();
}
}
}
return null;
return replaceNullOriginal( target, session );
}
if ( !Hibernate.isInitialized( original ) ) {
if ( ( (PersistentCollection<?>) original ).hasQueuedOperations() ) {
if ( original == target ) {
// A managed entity with an uninitialized collection is being merged,
// We need to replace any detached entities in the queued operations
// with managed copies.
final AbstractPersistentCollection<?> pc = (AbstractPersistentCollection<?>) original;
pc.replaceQueuedOperationValues( getPersister( session ), copyCache );
}
else {
// original is a detached copy of the collection;
// it contains queued operations, which will be ignored
LOG.ignoreQueuedOperationsOnMerge(
MessageHelper.collectionInfoString(
getRole(),
( (PersistentCollection<?>) original ).getKey()
)
);
}
}
return target;
else if ( !Hibernate.isInitialized( original ) ) {
return replaceUninitializedOriginal( original, target, session, copyCache );
}
else {
return replaceOriginal( original, target, session, owner, copyCache );
}
}
// for a null target, or a target which is the same as the original, we
// need to put the merged elements in a new collection
Object result = ( target == null ||
target == original ||
target == UNFETCHED_PROPERTY ||
target instanceof PersistentCollection
&& ( (PersistentCollection<?>) target ).isWrapper( original ) )
? instantiateResult( original ) : target;
private Object replaceOriginal(
Object original, Object target,
SharedSessionContractImplementor session,
Object owner,
Map<Object, Object> copyCache) {
//for arrays, replaceElements() may return a different reference, since
//the array length might not match
result = replaceElements( original, result, owner, copyCache, session );
final Object result =
replaceElements( original,
instantiateResultIfNecessary( original, target ),
owner, copyCache, session );
if ( original == target ) {
// get the elements back into the target making sure to handle dirty flag
boolean wasClean = target instanceof PersistentCollection
&& !( (PersistentCollection<?>) target ).isDirty();
final boolean wasClean =
target instanceof PersistentCollection<?> collection
&& !collection.isDirty();
//TODO: this is a little inefficient, don't need to do a whole
// deep replaceElements() call
replaceElements( result, target, owner, copyCache, session );
if ( wasClean ) {
( (PersistentCollection<?>) target ).clearDirty();
((PersistentCollection<?>) target).clearDirty();
}
result = target;
return target;
}
else {
return result;
}
}
return result;
private Object instantiateResultIfNecessary(Object original, Object target) {
// for a null target, or a target which is the same as the original,
// we need to put the merged elements in a new collection
return target == null
|| target == original
|| target == UNFETCHED_PROPERTY
|| target instanceof PersistentCollection<?> collection && collection.isWrapper( original )
? instantiateResult( original )
: target;
}
private Object replaceUninitializedOriginal(
Object original,
Object target,
SharedSessionContractImplementor session,
Map<Object, Object> copyCache) {
final PersistentCollection<?> persistentCollection = (PersistentCollection<?>) original;
if ( persistentCollection.hasQueuedOperations() ) {
if ( original == target ) {
// A managed entity with an uninitialized collection is being merged,
// We need to replace any detached entities in the queued operations
// with managed copies.
final AbstractPersistentCollection<?> pc = (AbstractPersistentCollection<?>) original;
pc.replaceQueuedOperationValues( getPersister( session ), copyCache );
}
else {
// original is a detached copy of the collection;
// it contains queued operations, which will be ignored
LOG.ignoreQueuedOperationsOnMerge(
collectionInfoString( getRole(), persistentCollection.getKey() ) );
}
}
return target;
}
private static Object replaceNullOriginal(Object target, SharedSessionContractImplementor session) {
if ( target == null ) {
return null;
}
else if ( target instanceof Collection<?> collection ) {
collection.clear();
return collection;
}
else if ( target instanceof Map<?,?> map ) {
map.clear();
return map;
}
else {
final PersistenceContext persistenceContext = session.getPersistenceContext();
final PersistentCollection<?> collectionHolder = persistenceContext.getCollectionHolder( target );
if ( collectionHolder != null ) {
if ( collectionHolder instanceof PersistentArrayHolder<?> arrayHolder ) {
persistenceContext.removeCollectionHolder( target );
arrayHolder.beginRead();
final PluralAttributeMapping attributeMapping =
persistenceContext.getCollectionEntry( collectionHolder )
.getLoadedPersister().getAttributeMapping();
arrayHolder.injectLoadedState( attributeMapping, null );
arrayHolder.endRead();
arrayHolder.dirty();
persistenceContext.addCollectionHolder( collectionHolder );
return arrayHolder.getArray();
}
}
}
return null;
}
/**
@ -739,65 +767,58 @@ public abstract class CollectionType extends AbstractType implements Association
* @return The collection
*/
public Object getCollection(Object key, SharedSessionContractImplementor session, Object owner, Boolean overridingEager) {
final CollectionPersister persister = getPersister( session );
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final CollectionKey collectionKey = new CollectionKey( persister, key );
PersistentCollection<?> collection = null;
// check if collection is currently being loaded
final LoadingCollectionEntry loadingCollectionEntry = persistenceContext.getLoadContexts().findLoadingCollectionEntry( collectionKey );
if ( loadingCollectionEntry != null ) {
collection = loadingCollectionEntry.getCollectionInstance();
}
final LoadingCollectionEntry loadingCollectionEntry =
persistenceContext.getLoadContexts().findLoadingCollectionEntry( collectionKey );
PersistentCollection<?> collection =
loadingCollectionEntry == null ? null
: loadingCollectionEntry.getCollectionInstance();
if ( collection == null ) {
// check if it is already completely loaded, but unowned
collection = persistenceContext.useUnownedCollection( collectionKey );
if ( collection == null ) {
collection = persistenceContext.getCollection( collectionKey );
if ( collection == null ) {
// create a new collection wrapper, to be initialized later
collection = instantiate( session, persister, key );
collection.setOwner( owner );
persistenceContext.addUninitializedCollection( persister, collection, key );
// some collections are not lazy:
boolean eager = overridingEager != null ? overridingEager : !persister.isLazy();
if ( initializeImmediately() ) {
session.initializeCollection( collection, false );
}
else if ( eager ) {
persistenceContext.addNonLazyCollection( collection );
}
if ( hasHolder() ) {
persistenceContext.addCollectionHolder( collection );
}
if ( LOG.isTraceEnabled() ) {
LOG.tracef( "Created collection wrapper: %s",
MessageHelper.collectionInfoString( persister, collection,
key, session ) );
}
// we have already set the owner so we can just return the value
return collection.getValue();
return createNewWrapper( key, owner, overridingEager, persister, session ).getValue();
}
}
}
collection.setOwner( owner );
return collection.getValue();
}
private PersistentCollection<?> createNewWrapper(
Object key,
Object owner,
Boolean overridingEager,
CollectionPersister persister,
SharedSessionContractImplementor session) {
final PersistenceContext persistenceContext = session.getPersistenceContextInternal();
final PersistentCollection<?> collection = instantiate( session, persister, key );
collection.setOwner( owner );
persistenceContext.addUninitializedCollection( persister, collection, key );
// some collections are not lazy:
if ( initializeImmediately() ) {
session.initializeCollection( collection, false );
}
else if ( overridingEager != null ? overridingEager : !persister.isLazy() ) {
persistenceContext.addNonLazyCollection( collection );
}
if ( hasHolder() ) {
persistenceContext.addCollectionHolder( collection );
}
if ( LOG.isTraceEnabled() ) {
LOG.tracef( "Created collection wrapper: %s",
collectionInfoString( persister, collection, key, session ) );
}
return collection;
}
public boolean hasHolder() {
return false;
}
@ -823,6 +844,6 @@ public abstract class CollectionType extends AbstractType implements Association
@Override
public boolean[] toColumnNullness(Object value, MappingContext mapping) {
return ArrayHelper.EMPTY_BOOLEAN_ARRAY;
return EMPTY_BOOLEAN_ARRAY;
}
}