From 2c08833c6d0ca064d3b0c2da98dab3bc86b5224b Mon Sep 17 00:00:00 2001 From: Gavin King Date: Sun, 17 Nov 2024 11:30:25 +0100 Subject: [PATCH] clean up CollectionType --- .../org/hibernate/type/CollectionType.java | 493 +++++++++--------- 1 file changed, 257 insertions(+), 236 deletions(-) diff --git a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java index 0e7409fb01..aa0b53d1e6 100644 --- a/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java +++ b/hibernate-core/src/main/java/org/hibernate/type/CollectionType.java @@ -10,8 +10,6 @@ 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.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.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 boolean contains(Object collection, Object childObject, SharedSessionCont // 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 boolean isCollectionType() { @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 void nullSafeSet(PreparedStatement st, Object value, int index, @Override public int[] getSqlTypeCodes(MappingContext mappingContext) throws MappingException { - return ArrayHelper.EMPTY_INT_ARRAY; + return EMPTY_INT_ARRAY; } @Override @@ -174,43 +173,43 @@ public String toLoggableString(Object value, SessionFactoryImplementor factory) 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 ""; } - - final List 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) - ? "" - : elemType.toLoggableString( element, factory ); - list.add( string ); + else { + final List 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 ) + ? "" + : elemType.toLoggableString( element, factory ); } @Override @@ -303,25 +302,24 @@ public Object assemble(Serializable cached, SharedSessionContractImplementor ses * @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 Object getIdOfOwnerOrNull(Object key, SharedSessionContractImplementor se 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 String getRHSUniqueKeyPropertyName() { @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 boolean isModified(Object old, Object current, boolean[] checkable, Share @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 Object replaceElements( Object owner, Map 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 Object replaceElements( // 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 @@ private void preserveSnapshot( Object owner, Map copyCache, SharedSessionContractImplementor session) { - Serializable originalSnapshot = original.getStoredSnapshot(); - Serializable resultSnapshot = result.getStoredSnapshot(); - Serializable targetSnapshot; - - if ( originalSnapshot instanceof List ) { - ArrayList 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 targetMap; - if ( originalSnapshot instanceof SortedMap ) { - @SuppressWarnings({"unchecked", "rawtypes"}) - Comparator 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 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 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 copyCache, + SharedSessionContractImplementor session) { + final Map resultSnapshot = (Map) result.getStoredSnapshot(); + final Map 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 createListSnapshot( + List list, + Type elemType, + Object owner, + Map copyCache, + SharedSessionContractImplementor session) { + final ArrayList 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 Object replace( final Object owner, final Map 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 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 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 String toString() { * @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 boolean isAlwaysDirtyChecked() { @Override public boolean[] toColumnNullness(Object value, MappingContext mapping) { - return ArrayHelper.EMPTY_BOOLEAN_ARRAY; + return EMPTY_BOOLEAN_ARRAY; } }