diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java index f28aac95f7..10160fe5dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/mapping/internal/MappingModelCreationProcess.java @@ -16,6 +16,7 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.ModelPart; import org.hibernate.metamodel.mapping.NonTransientException; import org.hibernate.metamodel.model.domain.NavigableRole; +import org.hibernate.metamodel.model.domain.internal.EntityPersisterConcurrentMap; import org.hibernate.metamodel.spi.RuntimeModelCreationContext; import org.hibernate.persister.entity.EntityPersister; import org.hibernate.query.sqm.function.SqmFunctionRegistry; @@ -33,7 +34,7 @@ public class MappingModelCreationProcess { * Triggers creation of the mapping model */ public static void process( - Map entityPersisterMap, + EntityPersisterConcurrentMap entityPersisterMap, RuntimeModelCreationContext creationContext) { final MappingModelCreationProcess process = new MappingModelCreationProcess( entityPersisterMap, @@ -42,14 +43,14 @@ public class MappingModelCreationProcess { process.execute(); } - private final Map entityPersisterMap; + private final EntityPersisterConcurrentMap entityPersisterMap; private final RuntimeModelCreationContext creationContext; private String currentlyProcessingRole; private List postInitCallbacks; private MappingModelCreationProcess( - Map entityPersisterMap, + EntityPersisterConcurrentMap entityPersisterMap, RuntimeModelCreationContext creationContext) { this.entityPersisterMap = entityPersisterMap; this.creationContext = creationContext; diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityPersisterConcurrentMap.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityPersisterConcurrentMap.java new file mode 100644 index 0000000000..3a21edab98 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/EntityPersisterConcurrentMap.java @@ -0,0 +1,104 @@ +/* + * 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.metamodel.model.domain.internal; + +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +import org.hibernate.persister.entity.EntityPersister; + +/** + * Concurrent Map implementation of mappings entity name -> EntityPersister. + * Concurrency is optimised for read operations; write operations will + * acquire a lock and are relatively costly: only use for long living, + * read-mostly use cases. + * This implementation attempts to avoid type pollution problems. + */ +public final class EntityPersisterConcurrentMap { + + private final ConcurrentHashMap map = new ConcurrentHashMap<>(); + private volatile EntityPersister[] values = new EntityPersister[0]; + private volatile String[] keys = new String[0]; + + public EntityPersister get(final String name) { + final EntityPersisterHolder entityPersisterHolder = map.get( name ); + if ( entityPersisterHolder != null ) { + return entityPersisterHolder.entityPersister; + } + return null; + } + + public EntityPersister[] values() { + return values; + } + + public synchronized void put(final String name, final EntityPersister entityPersister) { + map.put( name, new EntityPersisterHolder( entityPersister ) ); + recomputeValues(); + } + + public synchronized void putIfAbsent(final String name, final EntityPersister entityPersister) { + map.putIfAbsent( name, new EntityPersisterHolder( entityPersister ) ); + recomputeValues(); + } + + public boolean containsKey(final String name) { + return map.containsKey( name ); + } + + public String[] keys() { + return keys; + } + + private void recomputeValues() { + //Assumption: the write lock is being held (synchronize on this) + final int size = map.size(); + final EntityPersister[] newValues = new EntityPersister[size]; + final String[] newKeys = new String[size]; + int i = 0; + for ( Map.Entry e : map.entrySet() ) { + newValues[i] = e.getValue().entityPersister; + newKeys[i] = e.getKey(); + i++; + } + this.values = newValues; + this.keys = newKeys; + } + + /** + * @deprecated Higly inefficient - do not use; this exists + * to support other deprecated methods and will be removed. + */ + @Deprecated(forRemoval = true) + public Map convertToMap() { + return map.entrySet().stream().collect( Collectors.toUnmodifiableMap( + Map.Entry::getKey, + e -> e.getValue().entityPersister + ) ); + } + + /** + * Implementation note: since EntityPersister is an highly used + * interface, we intentionally avoid using a generic Map referring + * to it to avoid type pollution. + * Using a concrete holder class bypasses the problem, at a minimal + * tradeoff of memory. + */ + private final static class EntityPersisterHolder { + + private final EntityPersister entityPersister; + + EntityPersisterHolder(final EntityPersister entityPersister) { + Objects.requireNonNull( entityPersister ); + this.entityPersister = entityPersister; + } + + } + +} diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java index 80f06289bf..640d6aa7ec 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/MappingMetamodelImpl.java @@ -115,7 +115,7 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // RuntimeModel - private final Map entityPersisterMap = new ConcurrentHashMap<>(); + private final EntityPersisterConcurrentMap entityPersisterMap = new EntityPersisterConcurrentMap(); private final Map collectionPersisterMap = new ConcurrentHashMap<>(); private final Map> collectionRolesByEntityParticipant = new ConcurrentHashMap<>(); @@ -375,12 +375,14 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl @Override public void forEachEntityDescriptor(Consumer action) { - entityPersisterMap.values().forEach( action ); + for ( EntityPersister value : entityPersisterMap.values() ) { + action.accept( value ); + } } @Override public Stream streamEntityDescriptors() { - return entityPersisterMap.values().stream(); + return Arrays.stream( entityPersisterMap.values() ); } @Override @@ -547,7 +549,7 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl @Override @SuppressWarnings("deprecation") public Map entityPersisters() { - return entityPersisterMap; + return entityPersisterMap.convertToMap(); } @Override @SuppressWarnings("deprecation") @@ -634,7 +636,7 @@ public class MappingMetamodelImpl extends QueryParameterBindingTypeResolverImpl @Override public String[] getAllEntityNames() { - return ArrayHelper.toStringArray( entityPersisterMap.keySet() ); + return entityPersisterMap.keys(); } @Override