diff --git a/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LinkedIdentityHashMap.java b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LinkedIdentityHashMap.java new file mode 100644 index 0000000000..ca3905fc6e --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/internal/util/collections/LinkedIdentityHashMap.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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.internal.util.collections; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Utility {@link IdentityHashMap} implementation that maintains predictable iteration order, + * with special care for keys iteration efficiency, and uses reference equality + * (i.e. {@code ==}) in place of object-equality when comparing keys. + *

+ * Note that the {@link #keySet()}, {@link #values()} and {@link #entrySet()} methods for this class + * return unmodifiable collections and are only meant for iteration. + * + * @author Marco Belladelli + */ +public class LinkedIdentityHashMap extends IdentityHashMap { + private final ArraySet orderedKeys = new ArraySet<>(); + + @Override + public V put(K key, V value) { + orderedKeys.add( key ); + return super.put( key, value ); + } + + @Override + public V remove(Object key) { + orderedKeys.remove( key ); + return super.remove( key ); + } + + @Override + public boolean remove(Object key, Object value) { + final boolean removed = super.remove( key, value ); + if ( removed ) { + orderedKeys.remove( key ); + } + return removed; + } + + @Override + public void clear() { + orderedKeys.clear(); + super.clear(); + } + + @Override + public Set keySet() { + return Collections.unmodifiableSet( orderedKeys ); + } + + @Override + public Collection values() { + return orderedKeys.stream().map( this::get ).collect( Collectors.toUnmodifiableList() ); + } + + @Override + public Set> entrySet() { + return Collections.unmodifiableSet( + (Set>) orderedKeys.stream() + .map( key -> Map.entry( key, get( key ) ) ) + .collect( Collectors.toCollection( LinkedHashSet::new ) ) + ); + } + + @Override + public Object clone() { + throw new UnsupportedOperationException(); + } + + private static final class ArraySet extends ArrayList implements Set { + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java index bb8846b47b..e636709f24 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/ParameterMetadataImpl.java @@ -9,18 +9,17 @@ package org.hibernate.query.internal; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; -import java.util.IdentityHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; -import jakarta.persistence.Parameter; import org.hibernate.QueryException; import org.hibernate.internal.util.StringHelper; import org.hibernate.internal.util.collections.CollectionHelper; +import org.hibernate.internal.util.collections.LinkedIdentityHashMap; import org.hibernate.internal.util.compare.ComparableComparator; import org.hibernate.query.BindableType; import org.hibernate.query.QueryParameter; @@ -29,6 +28,8 @@ import org.hibernate.query.spi.ParameterMetadataImplementor; import org.hibernate.query.spi.QueryParameterImplementor; import org.hibernate.query.sqm.tree.expression.SqmParameter; +import jakarta.persistence.Parameter; + /** * Encapsulates metadata about parameters encountered within a query. * @@ -98,7 +99,7 @@ public class ParameterMetadataImpl implements ParameterMetadataImplementor { this.labels = Collections.emptySet(); } else { - this.queryParameters = new IdentityHashMap<>(); + this.queryParameters = new LinkedIdentityHashMap<>(); if ( positionalQueryParameters != null ) { for ( QueryParameterImplementor value : positionalQueryParameters.values() ) { this.queryParameters.put( value, Collections.emptyList() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java index ed0e045373..59e7dcaa03 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryParameterBindingsImpl.java @@ -12,7 +12,6 @@ import java.util.IdentityHashMap; import java.util.Iterator; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; @@ -167,29 +166,30 @@ public class QueryParameterBindingsImpl implements QueryParameterBindings { } @Override - public QueryKey.ParameterBindingsMemento generateQueryKeyMemento(SharedSessionContractImplementor persistenceContext) { - final MutableCacheKeyImpl mutableCacheKey = new MutableCacheKeyImpl(parameterBindingMap.size()); + public QueryKey.ParameterBindingsMemento generateQueryKeyMemento(SharedSessionContractImplementor session) { + final MutableCacheKeyImpl mutableCacheKey = new MutableCacheKeyImpl( parameterBindingMap.size() ); + // We know that parameters are consumed in processing order, this ensures consistency of generated cache keys + parameterMetadata.visitParameters( queryParameter -> { + final QueryParameterBinding binding = parameterBindingMap.get( queryParameter ); + assert binding != null : "Found unbound query parameter while generating cache key"; - for ( Map.Entry, QueryParameterBinding> entry : parameterBindingMap.entrySet() ) { - final QueryParameterBinding binding = entry.getValue(); final MappingModelExpressible mappingType = determineMappingType( binding, - entry.getKey(), - persistenceContext + queryParameter, + session ); - if ( binding.isMultiValued() ) { for ( Object bindValue : binding.getBindValues() ) { assert bindValue != null; - mappingType.addToCacheKey( mutableCacheKey, bindValue, persistenceContext ); + mappingType.addToCacheKey( mutableCacheKey, bindValue, session ); } } else { final Object bindValue = binding.getBindValue(); - mappingType.addToCacheKey( mutableCacheKey, bindValue, persistenceContext ); + mappingType.addToCacheKey( mutableCacheKey, bindValue, session ); } - } - + } ); + // Finally, build the overall cache key return mutableCacheKey.build(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java index ca05056e31..c91606f87f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java @@ -13,6 +13,7 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; +import org.hibernate.internal.util.collections.LinkedIdentityHashMap; import org.hibernate.query.internal.QueryParameterNamedImpl; import org.hibernate.query.internal.QueryParameterPositionalImpl; import org.hibernate.query.spi.QueryParameterImplementor; @@ -45,7 +46,7 @@ public class DomainParameterXref { return empty(); } - final Map, List>> sqmParamsByQueryParam = new IdentityHashMap<>(); + final Map, List>> sqmParamsByQueryParam = new LinkedIdentityHashMap<>(); final int sqmParamCount = parameterResolutions.getSqmParameters().size(); final Map, QueryParameterImplementor> queryParamBySqmParam = new IdentityHashMap<>( sqmParamCount ); @@ -166,14 +167,6 @@ public class DomainParameterXref { return sqmParameters.size(); } - /** - * Get the mapping of all QueryParameters to the List of its corresponding - * SqmParameters - */ - public Map, List>> getSqmParamByQueryParam() { - return sqmParamsByQueryParam; - } - public SqmStatement.ParameterResolutions getParameterResolutions() { return parameterResolutions; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java index 5da4df90c2..bece556f97 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/internal/SqmUtil.java @@ -9,9 +9,9 @@ package org.hibernate.query.sqm.internal; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; -import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; import java.util.Map; @@ -31,12 +31,8 @@ import org.hibernate.metamodel.mapping.ForeignKeyDescriptor; import org.hibernate.metamodel.mapping.JdbcMapping; import org.hibernate.metamodel.mapping.MappingModelExpressible; import org.hibernate.metamodel.mapping.PluralAttributeMapping; -import org.hibernate.sql.exec.spi.JdbcParametersList; -import org.hibernate.type.JavaObjectType; -import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.query.IllegalQueryOperationException; import org.hibernate.query.IllegalSelectQueryException; -import org.hibernate.spi.NavigablePath; import org.hibernate.query.spi.QueryParameterBinding; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterImplementor; @@ -50,12 +46,16 @@ import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper; import org.hibernate.query.sqm.tree.expression.SqmParameter; import org.hibernate.query.sqm.tree.jpa.ParameterCollector; import org.hibernate.query.sqm.tree.select.SqmSelectStatement; +import org.hibernate.spi.NavigablePath; import org.hibernate.sql.ast.SqlTreeCreationException; import org.hibernate.sql.ast.tree.expression.JdbcParameter; import org.hibernate.sql.ast.tree.from.TableGroup; import org.hibernate.sql.exec.internal.JdbcParameterBindingImpl; import org.hibernate.sql.exec.internal.JdbcParameterBindingsImpl; import org.hibernate.sql.exec.spi.JdbcParameterBindings; +import org.hibernate.sql.exec.spi.JdbcParametersList; +import org.hibernate.type.JavaObjectType; +import org.hibernate.type.descriptor.converter.spi.BasicValueConverter; import org.hibernate.type.spi.TypeConfiguration; /** @@ -118,7 +118,7 @@ public class SqmUtil { final int queryParameterCount = domainParameterXref.getQueryParameterCount(); final Map, Map, List>> result = new IdentityHashMap<>( queryParameterCount ); - for ( Map.Entry, List>> entry : domainParameterXref.getSqmParamByQueryParam().entrySet() ) { + for ( Map.Entry, List>> entry : domainParameterXref.getQueryParameters().entrySet() ) { final QueryParameterImplementor queryParam = entry.getKey(); final List> sqmParams = entry.getValue(); @@ -211,7 +211,7 @@ public class SqmUtil { ); for ( Map.Entry, List>> entry : - domainParameterXref.getSqmParamByQueryParam().entrySet() ) { + domainParameterXref.getQueryParameters().entrySet() ) { final QueryParameterImplementor queryParam = entry.getKey(); final List> sqmParameters = entry.getValue(); @@ -480,7 +480,7 @@ public class SqmUtil { public void process(SqmParameter parameter) { if ( sqmParameters == null ) { - sqmParameters = new HashSet<>(); + sqmParameters = new LinkedHashSet<>(); } if ( parameter instanceof SqmJpaCriteriaParameterWrapper ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java index 8d8a60431c..7488c35b81 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sqm/tree/select/SqmSelectStatement.java @@ -7,7 +7,7 @@ package org.hibernate.query.sqm.tree.select; import java.util.Collections; -import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -113,7 +113,7 @@ public class SqmSelectStatement extends AbstractSqmSelectQuery implements parameters = null; } else { - parameters = new HashSet<>( this.parameters.size() ); + parameters = new LinkedHashSet<>( this.parameters.size() ); for ( SqmParameter parameter : this.parameters ) { parameters.add( parameter.copy( context ) ); } @@ -225,7 +225,7 @@ public class SqmSelectStatement extends AbstractSqmSelectQuery implements @Override public void addParameter(SqmParameter parameter) { if ( parameters == null ) { - parameters = new HashSet<>(); + parameters = new LinkedHashSet<>(); } parameters.add( parameter );