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 extends Entry>) 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 );