diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java index b6241cf0aa..67a2a16856 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/AvailableSettings.java @@ -1737,12 +1737,17 @@ public interface AvailableSettings extends org.hibernate.jpa.AvailableSettings { /** * Controls how the individual Loaders for an entity are created. * - * When `true` (the default), only the minimal set of Loaders are - * created. These include the handling for {@link org.hibernate.LockMode#READ} - * and {@link org.hibernate.LockMode#NONE} as well as specialized Loaders for - * merge and refresh handling. + * When `true` (the default), the loaders are only created on first + * access; this ensures that all access patterns which are not useful + * to the application are never instantiated, possibly saving a + * substantial amount of memory for applications having many entities. + * The only exception is the loader for LockMode.NONE, + * which will always be eagerly initialized; this is necessary to + * detect mapping errors. * - * `false` indicates that all loaders should be created up front + * `false` indicates that all loaders should be created up front; this + * will consume more memory but ensures all necessary memory is + * allocated right away. * * @since 5.3 */ diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java index 1d6081740e..19aa1b9389 100755 --- a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/CollectionHelper.java @@ -13,6 +13,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -291,4 +292,73 @@ public final class CollectionHelper { return properties; } + + /** + * Use to convert sets which will be retained for a long time, + * such as for the lifetime of the Hibernate ORM instance. + * The returned Set might be immutable, but there is no guarantee of this: + * consider it immutable but don't rely on this. + * The goal is to save memory. + * @param set + * @param + * @return + */ + public static Set toSmallSet(Set set) { + switch ( set.size() ) { + case 0: + return Collections.EMPTY_SET; + case 1: + return Collections.singleton( set.iterator().next() ); + default: + //TODO assert tests pass even if this is set to return an unmodifiable Set + return set; + } + } + + /** + * Use to convert Maps which will be retained for a long time, + * such as for the lifetime of the Hibernate ORM instance. + * The returned Map might be immutable, but there is no guarantee of this: + * consider it immutable but don't rely on this. + * The goal is to save memory. + * @param map + * @param + * @param + * @return + */ + public static Map toSmallMap(final Map map) { + switch ( map.size() ) { + case 0: + return Collections.EMPTY_MAP; + case 1: + Map.Entry entry = map.entrySet().iterator().next(); + return Collections.singletonMap( entry.getKey(), entry.getValue() ); + default: + //TODO assert tests pass even if this is set to return an unmodifiable Map + return map; + } + } + + /** + * Use to convert ArrayList instances which will be retained for a long time, + * such as for the lifetime of the Hibernate ORM instance. + * The returned List might be immutable, but there is no guarantee of this: + * consider it immutable but don't rely on this. + * The goal is to save memory. + * @param arrayList + * @param + * @return + */ + public static List toSmallList(ArrayList arrayList) { + switch ( arrayList.size() ) { + case 0: + return Collections.EMPTY_LIST; + case 1: + return Collections.singletonList( arrayList.get( 0 ) ); + default: + arrayList.trimToSize(); + return arrayList; + } + } + } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LazyIndexedMap.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LazyIndexedMap.java new file mode 100644 index 0000000000..1c04d400f5 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LazyIndexedMap.java @@ -0,0 +1,84 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.internal.util.collections; + +import java.util.Arrays; +import java.util.function.Function; + +/** + * This is an internal data structure designed for very specific needs; + * it will most often be used as a replacement for EnumMap, although + * the focus on the Enum aspect is modelled as an int primitive: + * think of using the ordinals of an Enum to simulate the EnumMap. + * Proper abstraction of the indexing strategy is left to subclasses. + *

+ * There are various reasons to not expose the Enum on this class API; + * the primary is that in some of the cases in which we need the pattern, + * there is the need to hold an additional couple of values beyond the + * ones modelled by the Enum, essentially having some extra keys in the map; + * this could be modelled by defining a new Enum but that's also not ideal. + *

+ * Another reason is that the goal of this class is to allow the host to + * save memory, as we typically need to keep references to many of these + * objects for a long time; being able to refer purely to an array is + * less practical but gets us benefits in memory layout and total retained + * memory. + * + * @param the types used to model the key + * @param the types used to model the value + */ +public abstract class LazyIndexedMap { + + private volatile Object[] values; + private static final Object NOT_INITIALIZED = new Object(); + + protected LazyIndexedMap(final int size) { + final Object[] vs = new Object[size]; + //could rely on null, then then we should ensure that the valueGenerator function never returns null. + //Seems safer to guard with a custom token: the memory impact is negligible since it's a constant, + //and we're not needing to create these objects frequently. + Arrays.fill( vs, NOT_INITIALIZED ); + this.values = vs; + } + + /** + * Both the index and the original key are requested for efficiency reasons. + * It is the responsibility of the caller to ensure there is a 1:1 matching relation between them. + * @param index storage index in the array + * @param originalKey the original key object, used to efficiently pass it into the valueGenerator function + * @param valueGenerator if no value was generated before for this index, then the valueGenerator is invoked to + * associate a new value and store it into the internal array at the provided index. + * @return the associated value to this index/key. + */ + protected V computeIfAbsent(final int index, final K1 originalKey, final Function valueGenerator) { + final Object value = this.values[index]; + if ( value != NOT_INITIALIZED ) { + return (V) value; + } + else { + return lockedComputeIfAbsent( index, originalKey, valueGenerator ); + } + } + + private synchronized V lockedComputeIfAbsent(final int index, final K1 originalKey, final Function valueGenerator) { + //Get a fresh copy from the volatile read, while holding the global pessimistic lock in this: + final Object[] values = this.values; + final Object value = values[index]; + //Check again + if ( value != NOT_INITIALIZED ) { + return (V) value; + } + else { + //Actually need to generate the value + final V generated = valueGenerator.apply( originalKey ); + values[index] = generated; + //re-write on the volatile reference to publish any changes to the array + this.values = values; + return generated; + } + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LockModeEnumMap.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LockModeEnumMap.java new file mode 100644 index 0000000000..9cddfd292f --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LockModeEnumMap.java @@ -0,0 +1,36 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.internal.util.collections; + +import java.util.function.Function; + +import org.hibernate.LockMode; + +/** + * A concurrent safe EnumMap<LockMode>, suitable to + * lazily associate values to the enum keys. + * This implementation favours fast read operations + * and low memory consumption over other metrics. + * + * Specifically designed with specific use cases in mind: + * do not overly reuse without good reasons. + * + * @param the value type to be associated with each key + */ +public final class LockModeEnumMap extends LazyIndexedMap { + + private static final int ENUM_DIMENSION = LockMode.values().length; + + public LockModeEnumMap() { + super( ENUM_DIMENSION ); + } + + public V computeIfAbsent(LockMode key, Function valueGenerator) { + return super.computeIfAbsent( key.ordinal(), key, valueGenerator ); + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractIdentifiableType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractIdentifiableType.java index fff0d058c9..96343cdafa 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractIdentifiableType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractIdentifiableType.java @@ -7,6 +7,7 @@ package org.hibernate.metamodel.model.domain; import java.io.Serializable; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.Consumer; @@ -331,13 +332,18 @@ public abstract class AbstractIdentifiableType throw new IllegalStateException( "Non-aggregated id attributes were already set" ); } - for ( SingularPersistentAttribute idAttribute : (Set) idAttributes ) { - if ( AbstractIdentifiableType.this == idAttribute.getDeclaringType() ) { - addAttribute( idAttribute ); - } + if ( idAttributes.isEmpty() ) { + AbstractIdentifiableType.this.nonAggregatedIdAttributes = Collections.EMPTY_SET; } + else { + for ( SingularPersistentAttribute idAttribute : (Set) idAttributes ) { + if ( AbstractIdentifiableType.this == idAttribute.getDeclaringType() ) { + addAttribute( idAttribute ); + } + } - AbstractIdentifiableType.this.nonAggregatedIdAttributes = (Set) idAttributes; + AbstractIdentifiableType.this.nonAggregatedIdAttributes = (Set) idAttributes; + } } @Override diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java index 1be37e14ba..e9816dc491 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/AbstractManagedType.java @@ -9,6 +9,7 @@ package org.hibernate.metamodel.model.domain; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; @@ -28,6 +29,7 @@ import javax.persistence.metamodel.SingularAttribute; import org.hibernate.graph.internal.SubGraphImpl; import org.hibernate.graph.spi.SubGraphImplementor; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.metamodel.RepresentationMode; import org.hibernate.metamodel.model.domain.internal.AttributeContainer; import org.hibernate.metamodel.model.domain.internal.DomainModelHelper; @@ -47,7 +49,7 @@ public abstract class AbstractManagedType private final RepresentationMode representationMode; private final Map> declaredSingularAttributes = new LinkedHashMap<>(); - private final Map> declaredPluralAttributes = new LinkedHashMap<>(); + private volatile Map> declaredPluralAttributes ; private final List subTypes = new ArrayList<>(); @@ -118,12 +120,21 @@ public abstract class AbstractManagedType @Override @SuppressWarnings("unchecked") public Set> getDeclaredAttributes() { - if ( declaredSingularAttributes.isEmpty() && declaredPluralAttributes.isEmpty() ) { + final boolean isDeclaredSingularAttributesEmpty = CollectionHelper.isEmpty( declaredSingularAttributes ); + final boolean isDeclaredPluralAttributes = CollectionHelper.isEmpty( declaredPluralAttributes ); + if ( isDeclaredSingularAttributesEmpty && isDeclaredPluralAttributes ) { return Collections.emptySet(); } - - final HashSet attributes = new LinkedHashSet( declaredSingularAttributes.values() ); - attributes.addAll( declaredPluralAttributes.values() ); + final HashSet attributes; + if ( !isDeclaredSingularAttributesEmpty ) { + attributes = new LinkedHashSet( declaredSingularAttributes.values() ); + if ( !isDeclaredPluralAttributes ) { + attributes.addAll( declaredPluralAttributes.values() ); + } + } + else { + attributes = new LinkedHashSet( declaredPluralAttributes.values() ); + } return attributes; } @@ -191,6 +202,9 @@ public abstract class AbstractManagedType } // next plural + if ( declaredPluralAttributes == null ) { + return null; + } attribute = declaredPluralAttributes.get( name ); //noinspection RedundantIfStatement if ( attribute != null ) { @@ -347,18 +361,17 @@ public abstract class AbstractManagedType @Override @SuppressWarnings("unchecked") public Set> getPluralAttributes() { - final Set attributes = getDeclaredPluralAttributes(); - + HashSet attributes = declaredPluralAttributes == null ? new HashSet>() : new HashSet>( declaredPluralAttributes.values() ); if ( getSuperType() != null ) { attributes.addAll( getSuperType().getPluralAttributes() ); } - return attributes; } @Override public Set> getDeclaredPluralAttributes() { - return new HashSet<>( declaredPluralAttributes.values() ); + return declaredPluralAttributes == null ? + Collections.EMPTY_SET : new HashSet<>( declaredPluralAttributes.values() ); } @Override @@ -380,8 +393,9 @@ public abstract class AbstractManagedType } @Override + @SuppressWarnings("unchecked") public PluralPersistentAttribute findDeclaredPluralAttribute(String name) { - return declaredPluralAttributes.get( name ); + return declaredPluralAttributes == null ? null : declaredPluralAttributes.get( name ); } private void checkTypeForPluralAttributes( @@ -429,7 +443,7 @@ public abstract class AbstractManagedType @Override @SuppressWarnings( "unchecked") public CollectionAttribute getDeclaredCollection(String name) { - final PluralAttribute attribute = findDeclaredPluralAttribute( name ); + final PluralPersistentAttribute attribute = findDeclaredPluralAttribute( name ); basicCollectionCheck( attribute, name ); return ( CollectionAttribute ) attribute; } @@ -476,7 +490,7 @@ public abstract class AbstractManagedType @Override @SuppressWarnings( "unchecked") public SetPersistentAttribute getDeclaredSet(String name) { - final PluralAttribute attribute = findDeclaredPluralAttribute( name ); + final PluralPersistentAttribute attribute = findDeclaredPluralAttribute( name ); basicSetCheck( attribute, name ); return (SetPersistentAttribute) attribute; } @@ -523,7 +537,7 @@ public abstract class AbstractManagedType @Override @SuppressWarnings("unchecked") public ListPersistentAttribute getDeclaredList(String name) { - final PluralAttribute attribute = findDeclaredPluralAttribute( name ); + final PluralPersistentAttribute attribute = findDeclaredPluralAttribute( name ); basicListCheck( attribute, name ); return (ListPersistentAttribute) attribute; } @@ -570,7 +584,7 @@ public abstract class AbstractManagedType @Override @SuppressWarnings("unchecked") public MapPersistentAttribute getDeclaredMap(String name) { - final PluralAttribute attribute = findDeclaredPluralAttribute( name ); + final PluralPersistentAttribute attribute = findDeclaredPluralAttribute( name ); basicMapCheck( attribute, name ); return (MapPersistentAttribute) attribute; } @@ -598,7 +612,7 @@ public abstract class AbstractManagedType @Override @SuppressWarnings("unchecked") public MapAttribute getDeclaredMap(String name, Class keyType, Class valueType) { - final PluralAttribute attribute = findDeclaredPluralAttribute( name ); + final PluralPersistentAttribute attribute = findDeclaredPluralAttribute( name ); checkMapValueType( attribute, name, valueType ); final MapAttribute mapAttribute = ( MapAttribute ) attribute; checkMapKeyType( mapAttribute, name, keyType ); @@ -645,7 +659,10 @@ public abstract class AbstractManagedType declaredSingularAttributes.put( attribute.getName(), (SingularPersistentAttribute) attribute ); } else if ( attribute instanceof PluralPersistentAttribute ) { - declaredPluralAttributes.put(attribute.getName(), (PluralPersistentAttribute) attribute ); + if ( AbstractManagedType.this.declaredPluralAttributes == null ) { + AbstractManagedType.this.declaredPluralAttributes = new HashMap<>(); + } + AbstractManagedType.this.declaredPluralAttributes.put( attribute.getName(), (PluralPersistentAttribute) attribute ); } else { throw new IllegalArgumentException( diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java index caecf101fb..6f7103fb96 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractEntityPersister.java @@ -78,6 +78,8 @@ import org.hibernate.engine.jdbc.env.spi.JdbcEnvironment; import org.hibernate.engine.jdbc.spi.JdbcServices; import org.hibernate.engine.spi.CachedNaturalIdValueSource; import org.hibernate.engine.spi.CascadeStyle; +import org.hibernate.engine.spi.CascadingAction; +import org.hibernate.engine.spi.CascadingActions; import org.hibernate.engine.spi.CollectionKey; import org.hibernate.engine.spi.EntityEntry; import org.hibernate.engine.spi.EntityEntryFactory; @@ -105,6 +107,8 @@ import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.FilterHelper; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.internal.util.collections.LockModeEnumMap; import org.hibernate.jdbc.Expectation; import org.hibernate.jdbc.Expectations; import org.hibernate.jdbc.TooManyRowsAffectedException; @@ -296,7 +300,7 @@ public abstract class AbstractEntityPersister private final boolean[] propertyUniqueness; private final boolean[] propertySelectable; - private final List lobProperties = new ArrayList<>(); + private final List lobProperties; //information about lazy properties of this class private final String[] lazyPropertyNames; @@ -333,10 +337,9 @@ public abstract class AbstractEntityPersister // dynamic filters attached to the class-level private final FilterHelper filterHelper; - private final Set affectingFetchProfileNames = new HashSet<>(); + private volatile Set affectingFetchProfileNames; - private final Map uniqueKeyLoaders = new HashMap(); - private final Map lockers = new HashMap(); + private final LockModeEnumMap lockers = new LockModeEnumMap<>(); // SQL strings private String sqlVersionSelectString; @@ -811,6 +814,7 @@ public abstract class AbstractEntityPersister ArrayList lazyTypes = new ArrayList(); ArrayList lazyColAliases = new ArrayList(); + final ArrayList lobPropertiesLocalCollector = new ArrayList<>(); iter = bootDescriptor.getPropertyClosureIterator(); i = 0; boolean foundFormula = false; @@ -873,12 +877,13 @@ public abstract class AbstractEntityPersister propertyUniqueness[i] = prop.getValue().isAlternateUniqueKey(); if ( prop.isLob() && dialect.forceLobAsLastValue() ) { - lobProperties.add( i ); + lobPropertiesLocalCollector.add( i ); } i++; } + this.lobProperties = CollectionHelper.toSmallList( lobPropertiesLocalCollector ); hasFormulaProperties = foundFormula; lazyPropertyColumnAliases = ArrayHelper.to2DStringArray( lazyColAliases ); lazyPropertyNames = ArrayHelper.toStringArray( lazyNames ); @@ -2148,25 +2153,12 @@ public abstract class AbstractEntityPersister } } - protected void initLockers() { - lockers.put( LockMode.READ, generateLocker( LockMode.READ ) ); - lockers.put( LockMode.UPGRADE, generateLocker( LockMode.UPGRADE ) ); - lockers.put( LockMode.UPGRADE_NOWAIT, generateLocker( LockMode.UPGRADE_NOWAIT ) ); - lockers.put( LockMode.UPGRADE_SKIPLOCKED, generateLocker( LockMode.UPGRADE_SKIPLOCKED ) ); - lockers.put( LockMode.FORCE, generateLocker( LockMode.FORCE ) ); - lockers.put( LockMode.PESSIMISTIC_READ, generateLocker( LockMode.PESSIMISTIC_READ ) ); - lockers.put( LockMode.PESSIMISTIC_WRITE, generateLocker( LockMode.PESSIMISTIC_WRITE ) ); - lockers.put( LockMode.PESSIMISTIC_FORCE_INCREMENT, generateLocker( LockMode.PESSIMISTIC_FORCE_INCREMENT ) ); - lockers.put( LockMode.OPTIMISTIC, generateLocker( LockMode.OPTIMISTIC ) ); - lockers.put( LockMode.OPTIMISTIC_FORCE_INCREMENT, generateLocker( LockMode.OPTIMISTIC_FORCE_INCREMENT ) ); - } - protected LockingStrategy generateLocker(LockMode lockMode) { return factory.getDialect().getLockingStrategy( this, lockMode ); } private LockingStrategy getLocker(LockMode lockMode) { - return (LockingStrategy) lockers.get( lockMode ); + return lockers.computeIfAbsent( lockMode, this::generateLocker ); } public void lock( @@ -4576,6 +4568,9 @@ public abstract class AbstractEntityPersister } public void registerAffectingFetchProfile(String fetchProfileName) { + if ( affectingFetchProfileNames == null ) { + this.affectingFetchProfileNames = new HashSet<>(); + } affectingFetchProfileNames.add( fetchProfileName ); } @@ -4590,9 +4585,12 @@ public abstract class AbstractEntityPersister @Override public boolean isAffectedByEnabledFetchProfiles(LoadQueryInfluencers loadQueryInfluencers) { - for ( String s : loadQueryInfluencers.getEnabledFetchProfileNames() ) { - if ( affectingFetchProfileNames.contains( s ) ) { - return true; + final Set fetchProfileNames = this.affectingFetchProfileNames; + if ( fetchProfileNames != null ) { + for ( String s : loadQueryInfluencers.getEnabledFetchProfileNames() ) { + if ( fetchProfileNames.contains( s ) ) { + return true; + } } } return false; diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java index f14cbcab7a..fd37e9955b 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/AbstractPropertyMapping.java @@ -41,7 +41,11 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { private static final CoreMessageLogger LOG = CoreLogging.messageLogger( AbstractPropertyMapping.class ); private final Map typesByPropertyPath = new HashMap<>(); - private final Set duplicateIncompatiblePaths = new HashSet<>(); + + //This field is only used during initialization, no need for threadsafety: + //FIXME get rid of the field, or at least clear it after boot? Not urgent as we typically won't initialize it at all. + private Set duplicateIncompatiblePaths = null; + private final Map columnsByPropertyPath = new HashMap<>(); private final Map columnReadersByPropertyPath = new HashMap<>(); private final Map columnReaderTemplatesByPropertyPath = new HashMap<>(); @@ -168,7 +172,7 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { String[] formulaTemplates, Mapping factory) { Type existingType = typesByPropertyPath.get( path ); - if ( existingType != null || duplicateIncompatiblePaths.contains( path ) ) { + if ( existingType != null || ( duplicateIncompatiblePaths != null && duplicateIncompatiblePaths.contains( path ) ) ) { // If types match or the new type is not an association type, there is nothing for us to do if ( type == existingType || existingType == null || !( type instanceof AssociationType ) ) { logDuplicateRegistration( path, existingType, type ); @@ -212,6 +216,9 @@ public abstract class AbstractPropertyMapping implements PropertyMapping { logIncompatibleRegistration( path, existingType, type ); } if ( commonType == null ) { + if ( duplicateIncompatiblePaths == null ) { + duplicateIncompatiblePaths = new HashSet<>(); + } duplicateIncompatiblePaths.add( path ); typesByPropertyPath.remove( path ); // Set everything to empty to signal action has to be taken! diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java index e6bcaf1b4d..87b66c20c1 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/JoinedSubclassEntityPersister.java @@ -641,8 +641,6 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister { subclassNamesBySubclassTable = buildSubclassNamesBySubclassTableMapping( persistentClass, factory ); - initLockers(); - initSubclassPropertyAliasesMap( persistentClass ); postConstruct( creationContext.getMetadata() ); diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java index 9cb182baa1..df09ba6224 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/SingleTableEntityPersister.java @@ -8,6 +8,7 @@ package org.hibernate.persister.entity; import java.io.Serializable; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -31,6 +32,7 @@ import org.hibernate.internal.FilterAliasGenerator; import org.hibernate.internal.util.MarkerObject; import org.hibernate.internal.util.MutableInteger; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Column; import org.hibernate.mapping.Formula; import org.hibernate.mapping.Join; @@ -103,7 +105,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { private final int[] subclassFormulaTableNumberClosure; // discriminator column - private final Map subclassesByDiscriminatorValue = new HashMap<>(); + private final Map subclassesByDiscriminatorValue; private final boolean forceDiscriminator; private final String discriminatorColumnName; private final String discriminatorColumnReaders; @@ -120,9 +122,10 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { private final String[][] constraintOrderedKeyColumnNames; //private final Map propertyTableNumbersByName = new HashMap(); - private final Map propertyTableNumbersByNameAndSubclass = new HashMap<>(); + private final Map propertyTableNumbersByNameAndSubclass; - private final Map sequentialSelectStringsByEntityName = new HashMap<>(); + //Efficiency note: try to not allocate an HashMap if we're not going to need it. + private final Map sequentialSelectStringsByEntityName; private static final Object NULL_DISCRIMINATOR = new MarkerObject( "" ); private static final Object NOT_NULL_DISCRIMINATOR = new MarkerObject( "" ); @@ -292,6 +295,8 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { isNullableSubclassTable = ArrayHelper.toBooleanArray( isNullables ); hasSequentialSelects = hasDeferred; + this.sequentialSelectStringsByEntityName = hasSequentialSelects ? new HashMap<>() : Collections.EMPTY_MAP; + // DISCRIMINATOR if ( persistentClass.isPolymorphic() ) { @@ -382,6 +387,9 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { ArrayList formulaJoinedNumbers = new ArrayList<>(); ArrayList propertyJoinNumbers = new ArrayList<>(); + final HashMap propertyTableNumbersByNameAndSubclassLocal = new HashMap<>(); + final Map subclassesByDiscriminatorValueLocal = new HashMap<>(); + iter = persistentClass.getSubclassPropertyClosureIterator(); while ( iter.hasNext() ) { Property prop = (Property) iter.next(); @@ -389,7 +397,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { propertyJoinNumbers.add( join ); //propertyTableNumbersByName.put( prop.getName(), join ); - propertyTableNumbersByNameAndSubclass.put( + propertyTableNumbersByNameAndSubclassLocal.put( prop.getPersistentClass().getEntityName() + '.' + prop.getName(), join ); @@ -405,6 +413,9 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { } } } + + propertyTableNumbersByNameAndSubclass = CollectionHelper.toSmallMap( propertyTableNumbersByNameAndSubclassLocal ); + subclassColumnTableNumberClosure = ArrayHelper.toIntArray( columnJoinNumbers ); subclassFormulaTableNumberClosure = ArrayHelper.toIntArray( formulaJoinedNumbers ); subclassPropertyTableNumberClosure = ArrayHelper.toIntArray( propertyJoinNumbers ); @@ -413,7 +424,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { subclassClosure = new String[subclassSpan]; subclassClosure[0] = getEntityName(); if ( persistentClass.isPolymorphic() ) { - addSubclassByDiscriminatorValue( discriminatorValue, getEntityName() ); + addSubclassByDiscriminatorValue( subclassesByDiscriminatorValueLocal, discriminatorValue, getEntityName() ); } // SUBCLASSES @@ -424,15 +435,16 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { Subclass sc = (Subclass) iter.next(); subclassClosure[k++] = sc.getEntityName(); if ( sc.isDiscriminatorValueNull() ) { - addSubclassByDiscriminatorValue( NULL_DISCRIMINATOR, sc.getEntityName() ); + addSubclassByDiscriminatorValue( subclassesByDiscriminatorValueLocal, NULL_DISCRIMINATOR, sc.getEntityName() ); } else if ( sc.isDiscriminatorValueNotNull() ) { - addSubclassByDiscriminatorValue( NOT_NULL_DISCRIMINATOR, sc.getEntityName() ); + addSubclassByDiscriminatorValue( subclassesByDiscriminatorValueLocal, NOT_NULL_DISCRIMINATOR, sc.getEntityName() ); } else { try { DiscriminatorType dtype = (DiscriminatorType) discriminatorType; addSubclassByDiscriminatorValue( + subclassesByDiscriminatorValueLocal, dtype.stringToObject( sc.getDiscriminatorValue() ), sc.getEntityName() ); @@ -447,7 +459,8 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { } } - initLockers(); + // Don't hold a reference to an empty HashMap: + this.subclassesByDiscriminatorValue = CollectionHelper.toSmallMap( subclassesByDiscriminatorValueLocal ); initSubclassPropertyAliasesMap( persistentClass ); @@ -455,7 +468,7 @@ public class SingleTableEntityPersister extends AbstractEntityPersister { } - private void addSubclassByDiscriminatorValue(Object discriminatorValue, String entityName) { + private static void addSubclassByDiscriminatorValue(Map subclassesByDiscriminatorValue, Object discriminatorValue, String entityName) { String mappedEntityName = subclassesByDiscriminatorValue.put( discriminatorValue, entityName ); if ( mappedEntityName != null ) { throw new MappingException( diff --git a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java index abb30ef9c5..60d0aed03e 100644 --- a/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java +++ b/hibernate-core/src/main/java/org/hibernate/persister/entity/UnionSubclassEntityPersister.java @@ -210,8 +210,6 @@ public class UnionSubclassEntityPersister extends AbstractEntityPersister { constraintOrderedKeyColumnNames = new String[][] {getIdentifierColumnNames()}; } - initLockers(); - initSubclassPropertyAliasesMap( persistentClass ); postConstruct( creationContext.getMetadata() ); diff --git a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java index 7462c60cc0..b3d054ba34 100644 --- a/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java +++ b/hibernate-core/src/main/java/org/hibernate/tuple/entity/EntityMetamodel.java @@ -29,6 +29,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.internal.CoreMessageLogger; import org.hibernate.internal.util.ReflectHelper; import org.hibernate.internal.util.collections.ArrayHelper; +import org.hibernate.internal.util.collections.CollectionHelper; import org.hibernate.mapping.Component; import org.hibernate.mapping.PersistentClass; import org.hibernate.mapping.Property; @@ -118,8 +119,8 @@ public class EntityMetamodel implements Serializable { private final boolean explicitPolymorphism; private final boolean inherited; private final boolean hasSubclasses; - private final Set subclassEntityNames = new HashSet(); - private final Map entityNameByInheritenceClassMap = new HashMap(); + private final Set subclassEntityNames; + private final Map entityNameByInheritenceClassMap; private final BytecodeEnhancementMetadata bytecodeEnhancementMetadata; @@ -395,19 +396,23 @@ public class EntityMetamodel implements Serializable { hasMutableProperties = foundMutable; iter = persistentClass.getSubclassIterator(); + final Set subclassEntityNamesLocal = new HashSet<>(); while ( iter.hasNext() ) { - subclassEntityNames.add( ( (PersistentClass) iter.next() ).getEntityName() ); + subclassEntityNamesLocal.add( ( (PersistentClass) iter.next() ).getEntityName() ); } - subclassEntityNames.add( name ); + subclassEntityNamesLocal.add( name ); + subclassEntityNames = CollectionHelper.toSmallSet( subclassEntityNamesLocal ); + HashMap entityNameByInheritenceClassMapLocal = new HashMap(); if ( persistentClass.hasPojoRepresentation() ) { - entityNameByInheritenceClassMap.put( persistentClass.getMappedClass(), persistentClass.getEntityName() ); + entityNameByInheritenceClassMapLocal.put( persistentClass.getMappedClass(), persistentClass.getEntityName() ); iter = persistentClass.getSubclassIterator(); while ( iter.hasNext() ) { final PersistentClass pc = ( PersistentClass ) iter.next(); - entityNameByInheritenceClassMap.put( pc.getMappedClass(), pc.getEntityName() ); + entityNameByInheritenceClassMapLocal.put( pc.getMappedClass(), pc.getEntityName() ); } } + entityNameByInheritenceClassMap = CollectionHelper.toSmallMap( entityNameByInheritenceClassMapLocal ); } private static GenerationStrategyPair buildGenerationStrategyPair( @@ -947,7 +952,7 @@ public class EntityMetamodel implements Serializable { * @return The mapped entity-name, or null if no such mapping was found. */ public String findEntityNameByEntityClass(Class inheritenceClass) { - return ( String ) entityNameByInheritenceClassMap.get( inheritenceClass ); + return entityNameByInheritenceClassMap.get( inheritenceClass ); } @Override