From 3ea5a066ede573db9b3d743d223985d8762cd100 Mon Sep 17 00:00:00 2001 From: Christian Beikov Date: Thu, 23 Dec 2021 00:30:04 +0100 Subject: [PATCH] Add query plan cache statistics for native queries and implement proper caching --- .../internal/JpaAttributeConverterImpl.java | 32 ++++ .../domain/internal/JpaMetamodelImpl.java | 14 +- .../internal/EntityDomainResultBuilder.java | 23 +++ .../internal/ScalarDomainResultBuilder.java | 24 +++ .../QueryInterpretationCacheDisabledImpl.java | 9 +- .../QueryInterpretationCacheStandardImpl.java | 8 + .../org/hibernate/query/results/Builders.java | 16 +- .../hibernate/query/results/FetchBuilder.java | 2 + .../ImplicitAttributeFetchBuilder.java | 26 ++++ .../query/results/ResultBuilder.java | 2 + .../query/results/ResultSetMappingImpl.java | 77 +++++++++- .../CompleteFetchBuilderBasicPart.java | 30 ++++ ...leteFetchBuilderEntityValuedModelPart.java | 32 ++++ .../CompleteResultBuilderBasicModelPart.java | 34 +++++ ...leteResultBuilderBasicValuedConverted.java | 61 ++++++-- ...pleteResultBuilderBasicValuedStandard.java | 34 +++++ ...mpleteResultBuilderCollectionStandard.java | 35 +++++ .../CompleteResultBuilderEntityJpa.java | 34 +++++ .../CompleteResultBuilderEntityStandard.java | 34 +++++ .../CompleteResultBuilderInstantiation.java | 26 ++++ .../DelayedFetchBuilderBasicPart.java | 29 ++++ .../AbstractFetchBuilderContainer.java | 39 +++++ .../results/dynamic/DynamicFetchBuilder.java | 2 + .../dynamic/DynamicFetchBuilderLegacy.java | 60 +++++++- .../dynamic/DynamicFetchBuilderStandard.java | 51 ++++++- .../results/dynamic/DynamicResultBuilder.java | 1 + .../DynamicResultBuilderAttribute.java | 31 ++++ .../DynamicResultBuilderBasicConverted.java | 78 ++++++---- .../DynamicResultBuilderBasicStandard.java | 42 +++++ .../DynamicResultBuilderEntityCalculated.java | 38 +++++ .../DynamicResultBuilderEntityStandard.java | 48 ++++++ .../DynamicResultBuilderInstantiation.java | 76 ++++++++- .../implicit/ImplicitFetchBuilderBasic.java | 27 ++++ .../ImplicitFetchBuilderEmbeddable.java | 46 ++++++ .../implicit/ImplicitFetchBuilderEntity.java | 45 ++++++ .../ImplicitFetchBuilderEntityPart.java | 80 ++++++++++ .../implicit/ImplicitFetchBuilderPlural.java | 27 ++++ .../ImplicitModelPartResultBuilderBasic.java | 30 ++++ ...licitModelPartResultBuilderEmbeddable.java | 27 ++++ .../ImplicitModelPartResultBuilderEntity.java | 30 ++++ .../query/sql/internal/NativeQueryImpl.java | 3 +- .../sql/spi/NonSelectInterpretationsKey.java | 37 ++++- .../sql/spi/SelectInterpretationsKey.java | 62 ++++++-- .../jdbc/spi/JdbcValuesMappingProducer.java | 3 + .../stat/internal/QueryStatisticsImpl.java | 4 + .../stat/internal/StatisticsImpl.java | 15 +- .../stat/spi/StatisticsImplementor.java | 13 +- .../QueryPlanCacheStatisticsTest.java | 128 ++++++++++++---- .../QueryPlanCacheStatisticsTest.java | 144 ------------------ 49 files changed, 1504 insertions(+), 265 deletions(-) create mode 100644 hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEntityPart.java delete mode 100644 hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java index e5f5d9fdfe..b55a0a99a5 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/convert/internal/JpaAttributeConverterImpl.java @@ -113,4 +113,36 @@ public class JpaAttributeConverterImpl implements JpaAttributeConverter getRelationalJavaTypeDescriptor() { return jdbcJtd; } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + JpaAttributeConverterImpl that = (JpaAttributeConverterImpl) o; + + if ( !attributeConverterBean.equals( that.attributeConverterBean ) ) { + return false; + } + if ( !converterJtd.equals( that.converterJtd ) ) { + return false; + } + if ( !domainJtd.equals( that.domainJtd ) ) { + return false; + } + return jdbcJtd.equals( that.jdbcJtd ); + } + + @Override + public int hashCode() { + int result = attributeConverterBean.hashCode(); + result = 31 * result + converterJtd.hashCode(); + result = 31 * result + domainJtd.hashCode(); + result = 31 * result + jdbcJtd.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java index cca84924be..f1bd8d405b 100644 --- a/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/metamodel/model/domain/internal/JpaMetamodelImpl.java @@ -60,6 +60,7 @@ import org.hibernate.persister.entity.Queryable; import org.hibernate.query.sqm.tree.domain.SqmPolymorphicRootDescriptor; import org.hibernate.type.descriptor.java.JavaType; import org.hibernate.type.descriptor.java.spi.DynamicModelJtd; +import org.hibernate.type.descriptor.java.spi.EntityJavaTypeDescriptor; import org.hibernate.type.spi.TypeConfiguration; /** @@ -195,7 +196,7 @@ public class JpaMetamodelImpl implements JpaMetamodel, Serializable { @Override public EmbeddableDomainType embeddable(Class cls) { final ManagedType type = jpaManagedTypeMap.get( cls ); - if ( !( type instanceof EntityDomainType ) ) { + if ( !( type instanceof EmbeddableDomainType ) ) { throw new IllegalArgumentException( "Not an embeddable: " + cls.getName() ); } //noinspection unchecked @@ -503,13 +504,16 @@ public class JpaMetamodelImpl implements JpaMetamodel, Serializable { if ( embeddable.getJavaType() != null && embeddable.getJavaType() != Map.class ) { this.jpaEmbeddables.add( embeddable ); this.jpaManagedTypes.add( embeddable ); - this.jpaManagedTypeMap.put( embeddable.getJavaType(), embeddable ); + if ( !( embeddable.getExpressableJavaTypeDescriptor() instanceof EntityJavaTypeDescriptor ) ) { + this.jpaManagedTypeMap.put( embeddable.getJavaType(), embeddable ); + } } break; case ENABLED: this.jpaEmbeddables.add( embeddable ); this.jpaManagedTypes.add( embeddable ); - if ( embeddable.getJavaType() != null ) { + if ( embeddable.getJavaType() != null + && !( embeddable.getExpressableJavaTypeDescriptor() instanceof EntityJavaTypeDescriptor ) ) { this.jpaManagedTypeMap.put( embeddable.getJavaType(), embeddable ); } break; @@ -517,7 +521,9 @@ public class JpaMetamodelImpl implements JpaMetamodel, Serializable { if ( embeddable.getJavaType() == null ) { throw new UnsupportedOperationException( "ANY not supported" ); } - this.jpaManagedTypeMap.put( embeddable.getJavaType(), embeddable ); + if ( !( embeddable.getExpressableJavaTypeDescriptor() instanceof EntityJavaTypeDescriptor ) ) { + this.jpaManagedTypeMap.put( embeddable.getJavaType(), embeddable ); + } break; } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/EntityDomainResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/EntityDomainResultBuilder.java index 3d5a61aee7..b50c65c8f1 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/EntityDomainResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/EntityDomainResultBuilder.java @@ -50,6 +50,11 @@ public class EntityDomainResultBuilder implements ResultBuilder { return entityDescriptor.getJavaTypeDescriptor().getJavaTypeClass(); } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public EntityResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -77,4 +82,22 @@ public class EntityDomainResultBuilder implements ResultBuilder { domainResultCreationState ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final EntityDomainResultBuilder that = (EntityDomainResultBuilder) o; + return entityDescriptor.equals( that.entityDescriptor ); + } + + @Override + public int hashCode() { + return entityDescriptor.hashCode(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java index 833541f210..9601573ff9 100644 --- a/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/procedure/internal/ScalarDomainResultBuilder.java @@ -39,4 +39,28 @@ public class ScalarDomainResultBuilder implements ResultBuilder { DomainResultCreationState domainResultCreationState) { return new BasicResult<>( resultPosition, null, typeDescriptor ); } + + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + ScalarDomainResultBuilder that = (ScalarDomainResultBuilder) o; + + return typeDescriptor.equals( that.typeDescriptor ); + } + + @Override + public int hashCode() { + return typeDescriptor.hashCode(); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java index 7161d5fbe1..6ec6dc17c2 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheDisabledImpl.java @@ -47,7 +47,11 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation @Override public SelectQueryPlan resolveSelectQueryPlan(Key key, Supplier> creator) { - return null; + final StatisticsImplementor statistics = statisticsSupplier.get(); + if ( statistics.isStatisticsEnabled() ) { + statistics.queryPlanCacheMiss( key.getQueryString() ); + } + return creator.get(); } @Override @@ -61,7 +65,7 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation @Override public HqlInterpretation resolveHqlInterpretation(String queryString, Function> creator) { - StatisticsImplementor statistics = statisticsSupplier.get(); + final StatisticsImplementor statistics = statisticsSupplier.get(); final boolean stats = statistics.isStatisticsEnabled(); final long startTime = ( stats ) ? System.nanoTime() : 0L; final SqmStatement sqmStatement = creator.apply( queryString ); @@ -76,7 +80,6 @@ public class QueryInterpretationCacheDisabledImpl implements QueryInterpretation else { domainParameterXref = DomainParameterXref.from( sqmStatement ); parameterMetadata = new ParameterMetadataImpl( domainParameterXref.getQueryParameters() ); - } if ( stats ) { diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java index f472395df9..e53bbda9cb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryInterpretationCacheStandardImpl.java @@ -80,15 +80,23 @@ public class QueryInterpretationCacheStandardImpl implements QueryInterpretation Key key, Supplier> creator) { log.tracef( "QueryPlan#getSelectQueryPlan(%s)", key ); + final StatisticsImplementor statistics = statisticsSupplier.get(); + final boolean stats = statistics.isStatisticsEnabled(); @SuppressWarnings("unchecked") final SelectQueryPlan cached = (SelectQueryPlan) queryPlanCache.get( key ); if ( cached != null ) { + if ( stats ) { + statistics.queryPlanCacheHit( key.getQueryString() ); + } return cached; } final SelectQueryPlan plan = creator.get(); queryPlanCache.put( key.prepareForStore(), plan ); + if ( stats ) { + statistics.queryPlanCacheMiss( key.getQueryString() ); + } return plan; } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java b/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java index 336a53d879..6d2629b2d3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/Builders.java @@ -6,6 +6,8 @@ */ package org.hibernate.query.results; +import java.util.ArrayList; +import java.util.HashMap; import java.util.Locale; import jakarta.persistence.AttributeConverter; import jakarta.persistence.metamodel.EntityType; @@ -13,7 +15,6 @@ import jakarta.persistence.metamodel.SingularAttribute; import org.hibernate.LockMode; import org.hibernate.NotYetImplementedFor6Exception; -import org.hibernate.engine.FetchTiming; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.RuntimeMetamodels; import org.hibernate.metamodel.mapping.AttributeMapping; @@ -38,6 +39,7 @@ import org.hibernate.query.results.implicit.ImplicitFetchBuilder; import org.hibernate.query.results.implicit.ImplicitFetchBuilderBasic; import org.hibernate.query.results.implicit.ImplicitFetchBuilderEmbeddable; import org.hibernate.query.results.implicit.ImplicitFetchBuilderEntity; +import org.hibernate.query.results.implicit.ImplicitFetchBuilderEntityPart; import org.hibernate.query.results.implicit.ImplicitFetchBuilderPlural; import org.hibernate.query.results.implicit.ImplicitModelPartResultBuilderEntity; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -236,7 +238,7 @@ public class Builders { } public static DynamicFetchBuilderLegacy fetch(String tableAlias, String ownerTableAlias, String joinPropertyName) { - return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, joinPropertyName, null, null ); + return new DynamicFetchBuilderLegacy( tableAlias, ownerTableAlias, joinPropertyName, new ArrayList<>(), new HashMap<>() ); } public static ResultBuilder implicitEntityResultBuilder( @@ -273,15 +275,7 @@ public class Builders { } if ( fetchable instanceof EntityCollectionPart ) { - final EntityCollectionPart entityCollectionPart = (EntityCollectionPart) fetchable; - return (parent, fetchablePath, jdbcResultsMetadata, legacyFetchResolver, domainResultCreationState) -> parent.generateFetchableFetch( - entityCollectionPart, - fetchablePath, - FetchTiming.IMMEDIATE, - true, - null, - domainResultCreationState - ); + return new ImplicitFetchBuilderEntityPart( fetchPath, (EntityCollectionPart) fetchable ); } throw new UnsupportedOperationException(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java index 732471b256..666e0236c8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/FetchBuilder.java @@ -35,4 +35,6 @@ public interface FetchBuilder { default void visitFetchBuilders(BiConsumer consumer) { } + + FetchBuilder cacheKeyInstance(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ImplicitAttributeFetchBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/ImplicitAttributeFetchBuilder.java index 43fa5d2ea9..e582e784fb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ImplicitAttributeFetchBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ImplicitAttributeFetchBuilder.java @@ -32,6 +32,11 @@ public class ImplicitAttributeFetchBuilder implements FetchBuilder, ImplicitFetc this.attributeMapping = attributeMapping; } + @Override + public FetchBuilder cacheKeyInstance() { + return this; + } + @Override public Fetch buildFetch( FetchParent parent, @@ -50,4 +55,25 @@ public class ImplicitAttributeFetchBuilder implements FetchBuilder, ImplicitFetc domainResultCreationState ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final ImplicitAttributeFetchBuilder that = (ImplicitAttributeFetchBuilder) o; + return navigablePath.equals( that.navigablePath ) + && attributeMapping.equals( that.attributeMapping ); + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + attributeMapping.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java index e1d8672fb8..13a3efa470 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultBuilder.java @@ -31,6 +31,8 @@ public interface ResultBuilder { Class getJavaType(); + ResultBuilder cacheKeyInstance(); + default void visitFetchBuilders(BiConsumer consumer) { } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java index 1164198773..75f247869f 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/ResultSetMappingImpl.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.TreeSet; import java.util.function.BiConsumer; @@ -37,6 +38,7 @@ import org.hibernate.sql.results.graph.basic.BasicResult; import org.hibernate.sql.results.graph.embeddable.EmbeddableResultGraphNode; import org.hibernate.sql.results.graph.entity.EntityResult; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMapping; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; import org.hibernate.type.BasicType; @@ -47,12 +49,46 @@ import org.hibernate.type.BasicType; @Internal public class ResultSetMappingImpl implements ResultSetMapping { private final String mappingIdentifier; - + private final boolean isDynamic; private List resultBuilders; private Map> legacyFetchBuilders; public ResultSetMappingImpl(String mappingIdentifier) { + this( mappingIdentifier, false ); + } + + public ResultSetMappingImpl(String mappingIdentifier, boolean isDynamic) { this.mappingIdentifier = mappingIdentifier; + this.isDynamic = isDynamic; + } + + private ResultSetMappingImpl(ResultSetMappingImpl original) { + this.mappingIdentifier = original.mappingIdentifier; + this.isDynamic = original.isDynamic; + if ( !original.isDynamic || original.resultBuilders == null ) { + this.resultBuilders = null; + } + else { + final List resultBuilders = new ArrayList<>( original.resultBuilders.size() ); + for ( ResultBuilder resultBuilder : original.resultBuilders ) { + resultBuilders.add( resultBuilder.cacheKeyInstance() ); + } + this.resultBuilders = resultBuilders; + } + if ( !original.isDynamic || original.legacyFetchBuilders == null ) { + this.legacyFetchBuilders = null; + } + else { + final Map> legacyFetchBuilders = new HashMap<>( original.legacyFetchBuilders.size() ); + for ( Map.Entry> entry : original.legacyFetchBuilders.entrySet() ) { + final Map newValue = new HashMap<>( entry.getValue().size() ); + for ( Map.Entry builderEntry : entry.getValue().entrySet() ) { + newValue.put( builderEntry.getKey(), builderEntry.getValue().cacheKeyInstance() ); + } + legacyFetchBuilders.put( entry.getKey(), newValue ); + } + this.legacyFetchBuilders = legacyFetchBuilders; + } } @Override @@ -306,4 +342,43 @@ public class ResultSetMappingImpl implements ResultSetMapping { public NamedResultSetMappingMemento toMemento(String name) { throw new NotYetImplementedFor6Exception( getClass() ); } + + @Override + public JdbcValuesMappingProducer cacheKeyInstance() { + return new ResultSetMappingImpl( this ); + } + + @Override + public int hashCode() { + if ( isDynamic ) { + int result = mappingIdentifier != null ? mappingIdentifier.hashCode() : 0; + result = 31 * result + ( resultBuilders != null ? resultBuilders.hashCode() : 0 ); + result = 31 * result + ( legacyFetchBuilders != null ? legacyFetchBuilders.hashCode() : 0 ); + return result; + } + else { + return mappingIdentifier.hashCode(); + } + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final ResultSetMappingImpl that = (ResultSetMappingImpl) o; + if ( isDynamic ) { + return that.isDynamic + && Objects.equals( mappingIdentifier, that.mappingIdentifier ) + && Objects.equals( resultBuilders, that.resultBuilders ) + && Objects.equals( legacyFetchBuilders, that.legacyFetchBuilders ); + } + else { + return !that.isDynamic && mappingIdentifier.equals( that.mappingIdentifier ); + } + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderBasicPart.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderBasicPart.java index b7298bb15e..a744fc4a69 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderBasicPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderBasicPart.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.results.complete; +import java.util.Objects; import java.util.function.BiFunction; import org.hibernate.engine.FetchTiming; @@ -13,6 +14,7 @@ import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.BasicValuedFetchBuilder; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.MissingSqlSelectionException; import org.hibernate.query.results.PositionalSelectionsNotAllowedException; import org.hibernate.query.results.SqlSelectionImpl; @@ -45,6 +47,11 @@ public class CompleteFetchBuilderBasicPart implements CompleteFetchBuilder, Basi this.selectionAlias = selectionAlias; } + @Override + public FetchBuilder cacheKeyInstance() { + return this; + } + @Override public NavigablePath getNavigablePath() { return navigablePath; @@ -113,4 +120,27 @@ public class CompleteFetchBuilderBasicPart implements CompleteFetchBuilder, Basi domainResultCreationState ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final CompleteFetchBuilderBasicPart that = (CompleteFetchBuilderBasicPart) o; + return navigablePath.equals( that.navigablePath ) + && referencedModelPart.equals( that.referencedModelPart ) + && Objects.equals( selectionAlias, that.selectionAlias ); + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + referencedModelPart.hashCode(); + result = 31 * result + ( selectionAlias != null ? selectionAlias.hashCode() : 0 ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderEntityValuedModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderEntityValuedModelPart.java index 08519da654..568e645f37 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderEntityValuedModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteFetchBuilderEntityValuedModelPart.java @@ -12,6 +12,7 @@ import java.util.function.BiFunction; import org.hibernate.engine.FetchTiming; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.ast.spi.SqlExpressionResolver; @@ -46,6 +47,15 @@ public class CompleteFetchBuilderEntityValuedModelPart this.columnAliases = columnAliases; } + @Override + public FetchBuilder cacheKeyInstance() { + return new CompleteFetchBuilderEntityValuedModelPart( + navigablePath, + modelPart, + List.copyOf( columnAliases ) + ); + } + @Override public NavigablePath getNavigablePath() { return navigablePath; @@ -97,4 +107,26 @@ public class CompleteFetchBuilderEntityValuedModelPart ); } + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final CompleteFetchBuilderEntityValuedModelPart that = (CompleteFetchBuilderEntityValuedModelPart) o; + return navigablePath.equals( that.navigablePath ) + && modelPart.equals( that.modelPart ) + && columnAliases.equals( that.columnAliases ); + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + modelPart.hashCode(); + result = 31 * result + columnAliases.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicModelPart.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicModelPart.java index c74deb6fef..16b1fba7dc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicModelPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicModelPart.java @@ -11,6 +11,7 @@ import java.util.function.BiFunction; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.ast.spi.SqlExpressionResolver; @@ -59,6 +60,11 @@ public class CompleteResultBuilderBasicModelPart return modelPart; } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public BasicResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -91,4 +97,32 @@ public class CompleteResultBuilderBasicModelPart modelPart.getJavaTypeDescriptor() ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + CompleteResultBuilderBasicModelPart that = (CompleteResultBuilderBasicModelPart) o; + + if ( !navigablePath.equals( that.navigablePath ) ) { + return false; + } + if ( !modelPart.equals( that.modelPart ) ) { + return false; + } + return columnAlias.equals( that.columnAlias ); + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + modelPart.hashCode(); + result = 31 * result + columnAlias.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java index 45f56eafaa..6643a74345 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedConverted.java @@ -6,12 +6,14 @@ */ package org.hibernate.query.results.complete; +import java.util.Objects; import java.util.function.BiFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; @@ -37,10 +39,8 @@ import static org.hibernate.query.results.ResultsHelper.impl; */ public class CompleteResultBuilderBasicValuedConverted implements CompleteResultBuilderBasicValued { private final String explicitColumnName; - private final ManagedBean> converterBean; - private final JavaType> converterJtd; - private final BasicJavaType domainJavaType; private final BasicValuedMapping underlyingMapping; + private final JpaAttributeConverterImpl valueConverter; public CompleteResultBuilderBasicValuedConverted( String explicitColumnName, @@ -49,15 +49,23 @@ public class CompleteResultBuilderBasicValuedConverted implements CompleteR BasicJavaType domainJavaType, BasicValuedMapping underlyingMapping) { this.explicitColumnName = explicitColumnName; - this.converterBean = converterBean; - this.converterJtd = converterJtd; - this.domainJavaType = domainJavaType; this.underlyingMapping = underlyingMapping; + this.valueConverter = new JpaAttributeConverterImpl<>( + converterBean, + converterJtd, + domainJavaType, + underlyingMapping.getJdbcMapping().getJavaTypeDescriptor() + ); } @Override public Class getJavaType() { - return domainJavaType.getJavaTypeClass(); + return valueConverter.getDomainJavaDescriptor().getJavaTypeClass(); + } + + @Override + public ResultBuilder cacheKeyInstance() { + return this; } @Override @@ -123,22 +131,43 @@ public class CompleteResultBuilderBasicValuedConverted implements CompleteR return new SqlSelectionImpl( valuesArrayPosition, underlyingMapping ); } ), - domainJavaType, + valueConverter.getDomainJavaDescriptor(), sessionFactory.getTypeConfiguration() ); - final JpaAttributeConverterImpl valueConverter = new JpaAttributeConverterImpl<>( - converterBean, - converterJtd, - domainJavaType, - underlyingMapping.getJdbcMapping().getJavaTypeDescriptor() - ); - return new BasicResult<>( sqlSelection.getValuesArrayPosition(), columnName, - domainJavaType, + valueConverter.getDomainJavaDescriptor(), valueConverter ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + CompleteResultBuilderBasicValuedConverted that = (CompleteResultBuilderBasicValuedConverted) o; + + if ( !Objects.equals( explicitColumnName, that.explicitColumnName ) ) { + return false; + } + if ( !underlyingMapping.equals( that.underlyingMapping ) ) { + return false; + } + return valueConverter.equals( that.valueConverter ); + } + + @Override + public int hashCode() { + int result = explicitColumnName != null ? explicitColumnName.hashCode() : 0; + result = 31 * result + underlyingMapping.hashCode(); + result = 31 * result + valueConverter.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java index d52819b25d..3ad2f909c3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderBasicValuedStandard.java @@ -6,12 +6,14 @@ */ package org.hibernate.query.results.complete; +import java.util.Objects; import java.util.function.BiFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; @@ -52,6 +54,11 @@ public class CompleteResultBuilderBasicValuedStandard implements CompleteResultB this.explicitJavaTypeDescriptor = explicitJavaTypeDescriptor; } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public Class getJavaType() { return explicitJavaTypeDescriptor.getJavaTypeClass(); @@ -142,4 +149,31 @@ public class CompleteResultBuilderBasicValuedStandard implements CompleteResultB ); } + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + CompleteResultBuilderBasicValuedStandard that = (CompleteResultBuilderBasicValuedStandard) o; + + if ( !Objects.equals( explicitColumnName, that.explicitColumnName ) ) { + return false; + } + if ( !Objects.equals( explicitType, that.explicitType ) ) { + return false; + } + return explicitJavaTypeDescriptor.equals( that.explicitJavaTypeDescriptor ); + } + + @Override + public int hashCode() { + int result = explicitColumnName != null ? explicitColumnName.hashCode() : 0; + result = 31 * result + ( explicitType != null ? explicitType.hashCode() : 0 ); + result = 31 * result + explicitJavaTypeDescriptor.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderCollectionStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderCollectionStandard.java index d10dd854f9..e8f8569394 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderCollectionStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderCollectionStandard.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.results.complete; +import java.util.Arrays; import java.util.function.BiFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; @@ -19,6 +20,7 @@ import org.hibernate.query.NativeQuery; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.FromClauseAccessImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; @@ -84,6 +86,11 @@ public class CompleteResultBuilderCollectionStandard implements CompleteResultBu return navigablePath; } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public DomainResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -174,4 +181,32 @@ public class CompleteResultBuilderCollectionStandard implements CompleteResultBu } } + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final CompleteResultBuilderCollectionStandard that = (CompleteResultBuilderCollectionStandard) o; + return tableAlias.equals( that.tableAlias ) + && navigablePath.equals( that.navigablePath ) + && pluralAttributeDescriptor.equals( that.pluralAttributeDescriptor ) + && Arrays.equals( keyColumnNames, that.keyColumnNames ) + && Arrays.equals( indexColumnNames, that.indexColumnNames ) + && Arrays.equals( elementColumnNames, that.elementColumnNames ); + } + + @Override + public int hashCode() { + int result = tableAlias.hashCode(); + result = 31 * result + navigablePath.hashCode(); + result = 31 * result + pluralAttributeDescriptor.hashCode(); + result = 31 * result + Arrays.hashCode( keyColumnNames ); + result = 31 * result + Arrays.hashCode( indexColumnNames ); + result = 31 * result + Arrays.hashCode( elementColumnNames ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java index 0a75c93a15..da34ac3e76 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityJpa.java @@ -7,6 +7,7 @@ package org.hibernate.query.results.complete; import java.util.HashMap; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -17,6 +18,7 @@ import org.hibernate.query.NavigablePath; import org.hibernate.query.results.BasicValuedFetchBuilder; import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.FetchBuilder; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -77,6 +79,11 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti return entityDescriptor; } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public EntityResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -133,4 +140,31 @@ public class CompleteResultBuilderEntityJpa implements CompleteResultBuilderEnti public void visitFetchBuilders(BiConsumer consumer) { explicitFetchBuilderMap.forEach( consumer ); } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + entityDescriptor.hashCode(); + result = 31 * result + lockMode.hashCode(); + result = 31 * result + ( discriminatorFetchBuilder != null ? discriminatorFetchBuilder.hashCode() : 0 ); + result = 31 * result + explicitFetchBuilderMap.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final CompleteResultBuilderEntityJpa that = (CompleteResultBuilderEntityJpa) o; + return navigablePath.equals( that.navigablePath ) + && entityDescriptor.equals( that.entityDescriptor ) + && lockMode == that.lockMode + && Objects.equals( discriminatorFetchBuilder, that.discriminatorFetchBuilder ) + && explicitFetchBuilderMap.equals( that.explicitFetchBuilderMap ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityStandard.java index 6195e82636..4ed1d6391e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderEntityStandard.java @@ -7,6 +7,7 @@ package org.hibernate.query.results.complete; import java.util.HashMap; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -18,6 +19,7 @@ import org.hibernate.query.NavigablePath; import org.hibernate.query.results.BasicValuedFetchBuilder; import org.hibernate.query.results.DomainResultCreationStateImpl; import org.hibernate.query.results.FetchBuilder; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -105,6 +107,11 @@ public class CompleteResultBuilderEntityStandard implements CompleteResultBuilde throw new UnsupportedOperationException(); } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public EntityResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -161,4 +168,31 @@ public class CompleteResultBuilderEntityStandard implements CompleteResultBuilde public void visitFetchBuilders(BiConsumer consumer) { explicitFetchBuilderMap.forEach( consumer ); } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + entityDescriptor.hashCode(); + result = 31 * result + lockMode.hashCode(); + result = 31 * result + ( discriminatorFetchBuilder != null ? discriminatorFetchBuilder.hashCode() : 0 ); + result = 31 * result + explicitFetchBuilderMap.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final CompleteResultBuilderEntityStandard that = (CompleteResultBuilderEntityStandard) o; + return navigablePath.equals( that.navigablePath ) + && entityDescriptor.equals( that.entityDescriptor ) + && lockMode == that.lockMode + && Objects.equals( discriminatorFetchBuilder, that.discriminatorFetchBuilder ) + && explicitFetchBuilderMap.equals( that.explicitFetchBuilderMap ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderInstantiation.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderInstantiation.java index d05504d994..cec898cc0b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderInstantiation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/CompleteResultBuilderInstantiation.java @@ -44,6 +44,11 @@ public class CompleteResultBuilderInstantiation return javaTypeDescriptor.getJavaTypeClass(); } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public DomainResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -74,4 +79,25 @@ public class CompleteResultBuilderInstantiation argumentDomainResults ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final CompleteResultBuilderInstantiation that = (CompleteResultBuilderInstantiation) o; + return javaTypeDescriptor.equals( that.javaTypeDescriptor ) + && argumentResultBuilders.equals( that.argumentResultBuilders ); + } + + @Override + public int hashCode() { + int result = javaTypeDescriptor.hashCode(); + result = 31 * result + argumentResultBuilders.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/complete/DelayedFetchBuilderBasicPart.java b/hibernate-core/src/main/java/org/hibernate/query/results/complete/DelayedFetchBuilderBasicPart.java index dd407e086b..6cf4ee584d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/complete/DelayedFetchBuilderBasicPart.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/complete/DelayedFetchBuilderBasicPart.java @@ -12,6 +12,7 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.BasicValuedFetchBuilder; +import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.FetchParent; @@ -36,6 +37,11 @@ public class DelayedFetchBuilderBasicPart this.isEnhancedForLazyLoading = isEnhancedForLazyLoading; } + @Override + public FetchBuilder cacheKeyInstance() { + return this; + } + @Override public NavigablePath getNavigablePath() { return navigablePath; @@ -64,4 +70,27 @@ public class DelayedFetchBuilderBasicPart domainResultCreationState ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final DelayedFetchBuilderBasicPart that = (DelayedFetchBuilderBasicPart) o; + return isEnhancedForLazyLoading == that.isEnhancedForLazyLoading + && navigablePath.equals( that.navigablePath ) + && referencedModelPart.equals( that.referencedModelPart ); + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + referencedModelPart.hashCode(); + result = 31 * result + ( isEnhancedForLazyLoading ? 1 : 0 ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/AbstractFetchBuilderContainer.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/AbstractFetchBuilderContainer.java index 83f5950ec8..b9c1e8311d 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/AbstractFetchBuilderContainer.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/AbstractFetchBuilderContainer.java @@ -9,6 +9,7 @@ package org.hibernate.query.results.dynamic; import java.util.HashMap; import java.util.Locale; import java.util.Map; +import java.util.Objects; import org.hibernate.internal.util.collections.ArrayHelper; import org.hibernate.query.results.FetchBuilder; @@ -20,6 +21,26 @@ public abstract class AbstractFetchBuilderContainer fetchBuilderMap; + protected AbstractFetchBuilderContainer() { + } + + protected AbstractFetchBuilderContainer(AbstractFetchBuilderContainer original) { + if ( original.fetchBuilderMap != null ) { + final Map fetchBuilderMap = new HashMap<>( original.fetchBuilderMap.size() ); + for ( Map.Entry entry : original.fetchBuilderMap.entrySet() ) { + final DynamicFetchBuilder fetchBuilder; + if ( entry.getValue() instanceof DynamicFetchBuilderStandard ) { + fetchBuilder = ( (DynamicFetchBuilderStandard) entry.getValue() ).cacheKeyInstance( this ); + } + else { + fetchBuilder = entry.getValue().cacheKeyInstance(); + } + fetchBuilderMap.put( entry.getKey(), fetchBuilder ); + } + this.fetchBuilderMap = fetchBuilderMap; + } + } + protected abstract String getPropertyBase(); @Override @@ -79,4 +100,22 @@ public abstract class AbstractFetchBuilderContainer that = (AbstractFetchBuilderContainer) o; + return Objects.equals( fetchBuilderMap, that.fetchBuilderMap ); + } + + @Override + public int hashCode() { + return fetchBuilderMap != null ? fetchBuilderMap.hashCode() : 0; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilder.java index 7205eb5b4c..96a96b66f0 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilder.java @@ -16,5 +16,7 @@ import org.hibernate.sql.results.graph.Fetchable; * @author Steve Ebersole */ public interface DynamicFetchBuilder extends FetchBuilder, NativeQuery.ReturnProperty { + DynamicFetchBuilder cacheKeyInstance(); + List getColumnAliases(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java index 8eae4729e1..5aa102e07b 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderLegacy.java @@ -6,8 +6,10 @@ */ package org.hibernate.query.results.dynamic; +import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -59,12 +61,7 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue String fetchableName, List columnNames, Map fetchBuilderMap) { - this.tableAlias = tableAlias; - this.ownerTableAlias = ownerTableAlias; - this.fetchableName = fetchableName; - this.columnNames = columnNames; - this.fetchBuilderMap = fetchBuilderMap; - this.resultBuilderEntity = null; + this( tableAlias, ownerTableAlias, fetchableName, columnNames, fetchBuilderMap, null ); } public DynamicFetchBuilderLegacy( @@ -97,6 +94,28 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue return fetchableName; } + @Override + public DynamicFetchBuilderLegacy cacheKeyInstance() { + final Map fetchBuilderMap; + if ( this.fetchBuilderMap == null ) { + fetchBuilderMap = null; + } + else { + fetchBuilderMap = new HashMap<>( this.fetchBuilderMap.size() ); + for ( Map.Entry entry : this.fetchBuilderMap.entrySet() ) { + fetchBuilderMap.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); + } + } + return new DynamicFetchBuilderLegacy( + tableAlias, + ownerTableAlias, + fetchableName, + List.copyOf( columnNames ), + fetchBuilderMap, + resultBuilderEntity == null ? null : resultBuilderEntity.cacheKeyInstance() + ); + } + @Override public Fetch buildFetch( FetchParent parent, @@ -233,4 +252,33 @@ public class DynamicFetchBuilderLegacy implements DynamicFetchBuilder, NativeQue public void visitFetchBuilders(BiConsumer consumer) { fetchBuilderMap.forEach( consumer ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final DynamicFetchBuilderLegacy that = (DynamicFetchBuilderLegacy) o; + return tableAlias.equals( that.tableAlias ) + && ownerTableAlias.equals( that.ownerTableAlias ) + && fetchableName.equals( that.fetchableName ) + && Objects.equals( columnNames, that.columnNames ) + && Objects.equals( fetchBuilderMap, that.fetchBuilderMap ) + && Objects.equals( resultBuilderEntity, that.resultBuilderEntity ); + } + + @Override + public int hashCode() { + int result = tableAlias.hashCode(); + result = 31 * result + ownerTableAlias.hashCode(); + result = 31 * result + fetchableName.hashCode(); + result = 31 * result + ( columnNames != null ? columnNames.hashCode() : 0 ); + result = 31 * result + ( fetchBuilderMap != null ? fetchBuilderMap.hashCode() : 0 ); + result = 31 * result + ( resultBuilderEntity != null ? resultBuilderEntity.hashCode() : 0 ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderStandard.java index 69a6ec12a1..98409d5b2c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicFetchBuilderStandard.java @@ -18,6 +18,7 @@ import org.hibernate.metamodel.mapping.internal.ToOneAttributeMapping; import org.hibernate.query.NativeQuery; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.sql.ast.spi.SqlExpressionResolver; @@ -41,13 +42,40 @@ public class DynamicFetchBuilderStandard private final DynamicFetchBuilderContainer container; private final String fetchableName; - private final List columnNames = new ArrayList<>(); + private final List columnNames; public DynamicFetchBuilderStandard( DynamicFetchBuilderContainer container, String fetchableName) { this.container = container; this.fetchableName = fetchableName; + this.columnNames = new ArrayList<>(); + } + + private DynamicFetchBuilderStandard( + DynamicFetchBuilderContainer container, + String fetchableName, + List columnNames) { + this.container = container; + this.fetchableName = fetchableName; + this.columnNames = columnNames; + } + + @Override + public DynamicFetchBuilderStandard cacheKeyInstance() { + return new DynamicFetchBuilderStandard( + container, + fetchableName, + List.copyOf( columnNames ) + ); + } + + public DynamicFetchBuilderStandard cacheKeyInstance(DynamicFetchBuilderContainer container) { + return new DynamicFetchBuilderStandard( + container, + fetchableName, + List.copyOf( columnNames ) + ); } @Override @@ -134,4 +162,25 @@ public class DynamicFetchBuilderStandard public List getColumnAliases() { return columnNames; } + + @Override + public int hashCode() { + int result = fetchableName.hashCode(); + result = 31 * result + columnNames.hashCode(); + return result; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final DynamicFetchBuilderStandard that = (DynamicFetchBuilderStandard) o; + return fetchableName.equals( that.fetchableName ) + && columnNames.equals( that.columnNames ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilder.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilder.java index 264d1d4ffa..511cf7b53a 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilder.java @@ -22,4 +22,5 @@ import org.hibernate.query.results.ResultBuilder; * @author Steve Ebersole */ public interface DynamicResultBuilder extends ResultBuilder, NativeQuery.ReturnableResultNode { + DynamicResultBuilder cacheKeyInstance(); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderAttribute.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderAttribute.java index 40a520efe4..574a6bf1bb 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderAttribute.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderAttribute.java @@ -12,6 +12,7 @@ import java.util.function.BiFunction; import org.hibernate.metamodel.mapping.SingularAttributeMapping; import org.hibernate.metamodel.mapping.internal.BasicAttributeMapping; import org.hibernate.query.NativeQuery; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.sql.ast.spi.SqlExpressionResolver; import org.hibernate.sql.ast.spi.SqlSelection; @@ -65,6 +66,11 @@ public class DynamicResultBuilderAttribute implements DynamicResultBuilder, Nati throw new UnsupportedOperationException(); } + @Override + public DynamicResultBuilderAttribute cacheKeyInstance() { + return this; + } + @Override public DomainResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -97,4 +103,29 @@ public class DynamicResultBuilderAttribute implements DynamicResultBuilder, Nati attributeMapping.getValueConverter() ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final DynamicResultBuilderAttribute that = (DynamicResultBuilderAttribute) o; + return attributeMapping.equals( that.attributeMapping ) + && columnAlias.equals( that.columnAlias ) + && entityName.equals( that.entityName ) + && attributePath.equals( that.attributePath ); + } + + @Override + public int hashCode() { + int result = attributeMapping.hashCode(); + result = 31 * result + columnAlias.hashCode(); + result = 31 * result + entityName.hashCode(); + result = 31 * result + attributePath.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java index a1683f4058..a952e3efb3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicConverted.java @@ -6,6 +6,7 @@ */ package org.hibernate.query.results.dynamic; +import java.util.Objects; import java.util.function.BiFunction; import jakarta.persistence.AttributeConverter; @@ -13,6 +14,7 @@ import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; import org.hibernate.metamodel.model.convert.internal.JpaAttributeConverterImpl; import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.resource.beans.spi.ManagedBean; @@ -35,10 +37,6 @@ import org.hibernate.type.spi.TypeConfiguration; */ public class DynamicResultBuilderBasicConverted implements DynamicResultBuilderBasic { private final String columnAlias; - - private final JavaType domainJtd; - private final JavaType jdbcJtd; - private final BasicValueConverter basicValueConverter; public DynamicResultBuilderBasicConverted( @@ -49,20 +47,15 @@ public class DynamicResultBuilderBasicConverted implements DynamicResultBui SessionFactoryImplementor sessionFactory) { final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); final JavaTypeRegistry jtdRegistry = typeConfiguration.getJavaTypeDescriptorRegistry(); - - this.columnAlias = columnAlias; - this.domainJtd = jtdRegistry.getDescriptor( domainJavaType ); - this.jdbcJtd = jtdRegistry.getDescriptor( jdbcJavaType ); - - - final JavaType converterJtd = jtdRegistry.getDescriptor( converter.getClass() ); + final JavaType> converterJtd = jtdRegistry.getDescriptor( converter.getClass() ); final ManagedBean> bean = new ProvidedInstanceManagedBeanImpl<>( converter ); - this.basicValueConverter = new JpaAttributeConverterImpl( + this.columnAlias = columnAlias; + this.basicValueConverter = new JpaAttributeConverterImpl<>( bean, converterJtd, - domainJtd, - jdbcJtd + jtdRegistry.getDescriptor( domainJavaType ), + jtdRegistry.getDescriptor( jdbcJavaType ) ); } @@ -70,31 +63,31 @@ public class DynamicResultBuilderBasicConverted implements DynamicResultBui String columnAlias, Class domainJavaType, Class jdbcJavaType, - Class> converterJavaType, + Class> converterJavaType, SessionFactoryImplementor sessionFactory) { final ManagedBeanRegistry beans = sessionFactory.getServiceRegistry().getService( ManagedBeanRegistry.class ); final TypeConfiguration typeConfiguration = sessionFactory.getTypeConfiguration(); final JavaTypeRegistry jtdRegistry = typeConfiguration.getJavaTypeDescriptorRegistry(); + final JavaType> converterJtd = jtdRegistry.getDescriptor( converterJavaType ); + final ManagedBean> bean = beans.getBean( converterJavaType ); this.columnAlias = columnAlias; - this.domainJtd = jtdRegistry.getDescriptor( domainJavaType ); - this.jdbcJtd = jtdRegistry.getDescriptor( jdbcJavaType ); - - - final JavaType> converterJtd = jtdRegistry.getDescriptor( converterJavaType ); - final ManagedBean> bean = beans.getBean( converterJavaType ); - - this.basicValueConverter = new JpaAttributeConverterImpl( + this.basicValueConverter = new JpaAttributeConverterImpl<>( bean, converterJtd, - domainJtd, - jdbcJtd + jtdRegistry.getDescriptor( domainJavaType ), + jtdRegistry.getDescriptor( jdbcJavaType ) ); } @Override public Class getJavaType() { - return domainJtd.getJavaTypeClass(); + return basicValueConverter.getDomainJavaDescriptor().getJavaTypeClass(); + } + + @Override + public DynamicResultBuilderBasicConverted cacheKeyInstance() { + return this; } @Override @@ -134,10 +127,39 @@ public class DynamicResultBuilderBasicConverted implements DynamicResultBui return new SqlSelectionImpl( valuesArrayPosition, (BasicValuedMapping) basicType ); } ), - domainJtd, + basicValueConverter.getDomainJavaDescriptor(), typeConfiguration ); - return new BasicResult<>( sqlSelection.getValuesArrayPosition(), columnAlias, domainJtd, basicValueConverter ); + return new BasicResult<>( + sqlSelection.getValuesArrayPosition(), + columnAlias, + basicValueConverter.getDomainJavaDescriptor(), + basicValueConverter + ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + DynamicResultBuilderBasicConverted that = (DynamicResultBuilderBasicConverted) o; + + if ( !Objects.equals( columnAlias, that.columnAlias ) ) { + return false; + } + return basicValueConverter.equals( that.basicValueConverter ); + } + + @Override + public int hashCode() { + int result = columnAlias != null ? columnAlias.hashCode() : 0; + result = 31 * result + basicValueConverter.hashCode(); + return result; } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java index c32d51aa0e..ae29674af3 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderBasicStandard.java @@ -6,10 +6,12 @@ */ package org.hibernate.query.results.dynamic; +import java.util.Objects; import java.util.function.BiFunction; import org.hibernate.engine.spi.SessionFactoryImplementor; import org.hibernate.metamodel.mapping.BasicValuedMapping; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.sql.ast.spi.SqlExpressionResolver; @@ -107,6 +109,11 @@ public class DynamicResultBuilderBasicStandard implements DynamicResultBuilderBa return columnName; } + @Override + public DynamicResultBuilderBasicStandard cacheKeyInstance() { + return this; + } + @Override public BasicResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -166,4 +173,39 @@ public class DynamicResultBuilderBasicStandard implements DynamicResultBuilderBa return new BasicResult<>( sqlSelection.getValuesArrayPosition(), resultAlias, javaTypeDescriptor ); } + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + DynamicResultBuilderBasicStandard that = (DynamicResultBuilderBasicStandard) o; + + if ( columnPosition != that.columnPosition ) { + return false; + } + if ( !columnName.equals( that.columnName ) ) { + return false; + } + if ( !resultAlias.equals( that.resultAlias ) ) { + return false; + } + if ( !Objects.equals( explicitType, that.explicitType ) ) { + return false; + } + return Objects.equals( explicitJavaTypeDescriptor, that.explicitJavaTypeDescriptor ); + } + + @Override + public int hashCode() { + int result = columnName.hashCode(); + result = 31 * result + columnPosition; + result = 31 * result + resultAlias.hashCode(); + result = 31 * result + ( explicitType != null ? explicitType.hashCode() : 0 ); + result = 31 * result + ( explicitJavaTypeDescriptor != null ? explicitJavaTypeDescriptor.hashCode() : 0 ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java index 47f73ba3d5..6ce3b5b12e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityCalculated.java @@ -14,6 +14,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.query.NativeQuery; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.sql.ast.spi.SqlAliasBaseConstant; import org.hibernate.sql.ast.tree.from.TableGroup; @@ -99,6 +100,11 @@ public class DynamicResultBuilderEntityCalculated implements DynamicResultBuilde throw new UnsupportedOperationException(); } + @Override + public DynamicResultBuilderEntityCalculated cacheKeyInstance() { + return this; + } + @Override public EntityResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -130,4 +136,36 @@ public class DynamicResultBuilderEntityCalculated implements DynamicResultBuilde domainResultCreationState ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + DynamicResultBuilderEntityCalculated that = (DynamicResultBuilderEntityCalculated) o; + + if ( !navigablePath.equals( that.navigablePath ) ) { + return false; + } + if ( !entityMapping.equals( that.entityMapping ) ) { + return false; + } + if ( !tableAlias.equals( that.tableAlias ) ) { + return false; + } + return explicitLockMode == that.explicitLockMode; + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + entityMapping.hashCode(); + result = 31 * result + tableAlias.hashCode(); + result = 31 * result + ( explicitLockMode != null ? explicitLockMode.hashCode() : 0 ); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityStandard.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityStandard.java index c3ef2f8688..420d460acf 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityStandard.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderEntityStandard.java @@ -9,6 +9,7 @@ package org.hibernate.query.results.dynamic; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Function; @@ -22,6 +23,7 @@ import org.hibernate.metamodel.mapping.internal.SingleAttributeIdentifierMapping import org.hibernate.query.NativeQuery; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.query.results.TableGroupImpl; import org.hibernate.sql.ast.spi.FromClauseAccess; @@ -72,6 +74,16 @@ public class DynamicResultBuilderEntityStandard this.tableAlias = tableAlias; } + private DynamicResultBuilderEntityStandard(DynamicResultBuilderEntityStandard original) { + super( original ); + this.navigablePath = original.navigablePath; + this.entityMapping = original.entityMapping; + this.tableAlias = original.tableAlias; + this.lockMode = original.lockMode; + this.idColumnNames = original.idColumnNames == null ? null : List.copyOf( original.idColumnNames ); + this.discriminatorColumnName = original.discriminatorColumnName; + } + @Override public Class getJavaType() { return entityMapping.getJavaTypeDescriptor().getJavaTypeClass(); @@ -111,6 +123,11 @@ public class DynamicResultBuilderEntityStandard return entityMapping.getEntityName(); } + @Override + public DynamicResultBuilderEntityStandard cacheKeyInstance() { + return new DynamicResultBuilderEntityStandard( this ); + } + @Override public EntityResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -280,4 +297,35 @@ public class DynamicResultBuilderEntityStandard this.discriminatorColumnName = columnName; return this; } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = 31 * result + navigablePath.hashCode(); + result = 31 * result + entityMapping.hashCode(); + result = 31 * result + tableAlias.hashCode(); + result = 31 * result + ( lockMode != null ? lockMode.hashCode() : 0 ); + result = 31 * result + ( idColumnNames != null ? idColumnNames.hashCode() : 0 ); + result = 31 * result + ( discriminatorColumnName != null ? discriminatorColumnName.hashCode() : 0 ); + return result; + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final DynamicResultBuilderEntityStandard that = (DynamicResultBuilderEntityStandard) o; + return navigablePath.equals( that.navigablePath ) + && entityMapping.equals( that.entityMapping ) + && tableAlias.equals( that.tableAlias ) + && lockMode == that.lockMode + && Objects.equals( idColumnNames, that.idColumnNames ) + && Objects.equals( discriminatorColumnName, that.discriminatorColumnName ) + && super.equals( o ); + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderInstantiation.java b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderInstantiation.java index 93ad041403..34b1315765 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderInstantiation.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/dynamic/DynamicResultBuilderInstantiation.java @@ -13,6 +13,7 @@ import java.util.function.BiFunction; import org.hibernate.query.DynamicInstantiationNature; import org.hibernate.query.NativeQuery; import org.hibernate.query.results.Builders; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultBuilderInstantiationValued; import org.hibernate.sql.results.graph.DomainResult; import org.hibernate.sql.results.graph.DomainResultCreationState; @@ -35,13 +36,53 @@ public class DynamicResultBuilderInstantiation this.argumentBuilder = argumentBuilder; this.resultAlias = resultAlias; } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + InstantiationArgument that = (InstantiationArgument) o; + + if ( !argumentBuilder.equals( that.argumentBuilder ) ) { + return false; + } + return resultAlias.equals( that.resultAlias ); + } + + @Override + public int hashCode() { + int result = argumentBuilder.hashCode(); + result = 31 * result + resultAlias.hashCode(); + return result; + } } private final JavaType javaTypeDescriptor; - private final List argumentResultBuilders = new ArrayList<>(); + private final List argumentResultBuilders; public DynamicResultBuilderInstantiation(JavaType javaTypeDescriptor) { this.javaTypeDescriptor = javaTypeDescriptor; + this.argumentResultBuilders = new ArrayList<>(); + } + + private DynamicResultBuilderInstantiation(DynamicResultBuilderInstantiation original) { + this.javaTypeDescriptor = original.javaTypeDescriptor; + final List arguments = new ArrayList<>( original.argumentResultBuilders.size() ); + for ( InstantiationArgument argument : original.argumentResultBuilders ) { + arguments.add( + new InstantiationArgument( + argument.argumentBuilder.cacheKeyInstance(), + argument.resultAlias + ) + ); + } + + this.argumentResultBuilders = arguments; } @Override @@ -57,6 +98,11 @@ public class DynamicResultBuilderInstantiation return this; } + @Override + public DynamicResultBuilderInstantiation cacheKeyInstance() { + return new DynamicResultBuilderInstantiation( this ); + } + @Override public DomainResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -72,7 +118,7 @@ public class DynamicResultBuilderInstantiation for ( int i = 0; i < argumentResultBuilders.size(); i++ ) { final InstantiationArgument argument = argumentResultBuilders.get( i ); - final ArgumentDomainResult argumentDomainResult = new ArgumentDomainResult( + final ArgumentDomainResult argumentDomainResult = new ArgumentDomainResult<>( argument.argumentBuilder.buildResult( jdbcResultsMetadata, i, @@ -84,11 +130,35 @@ public class DynamicResultBuilderInstantiation argumentDomainResults.add( argumentDomainResult ); } - return new DynamicInstantiationResultImpl( + return new DynamicInstantiationResultImpl<>( null, DynamicInstantiationNature.CLASS, javaTypeDescriptor, argumentDomainResults ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + DynamicResultBuilderInstantiation that = (DynamicResultBuilderInstantiation) o; + + if ( !javaTypeDescriptor.equals( that.javaTypeDescriptor ) ) { + return false; + } + return argumentResultBuilders.equals( that.argumentResultBuilders ); + } + + @Override + public int hashCode() { + int result = javaTypeDescriptor.hashCode(); + result = 31 * result + argumentResultBuilders.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java index 732e84a295..cce202271e 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderBasic.java @@ -15,6 +15,7 @@ import org.hibernate.metamodel.model.convert.spi.BasicValueConverter; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.BasicValuedFetchBuilder; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.SqlSelectionImpl; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; @@ -41,6 +42,11 @@ public class ImplicitFetchBuilderBasic implements ImplicitFetchBuilder, BasicVal this.fetchable = fetchable; } + @Override + public FetchBuilder cacheKeyInstance() { + return this; + } + @Override public BasicFetch buildFetch( FetchParent parent, @@ -109,4 +115,25 @@ public class ImplicitFetchBuilderBasic implements ImplicitFetchBuilder, BasicVal public String toString() { return "ImplicitFetchBuilderBasic(" + fetchPath + ")"; } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final ImplicitFetchBuilderBasic that = (ImplicitFetchBuilderBasic) o; + return fetchPath.equals( that.fetchPath ) + && fetchable.equals( that.fetchable ); + } + + @Override + public int hashCode() { + int result = fetchPath.hashCode(); + result = 31 * result + fetchable.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEmbeddable.java index 1291b04a52..71c9e5e39c 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEmbeddable.java @@ -6,6 +6,8 @@ */ package org.hibernate.query.results.implicit; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.BiConsumer; @@ -67,6 +69,27 @@ public class ImplicitFetchBuilderEmbeddable implements ImplicitFetchBuilder { this.fetchBuilders = fetchBuilders; } + private ImplicitFetchBuilderEmbeddable(ImplicitFetchBuilderEmbeddable original) { + this.fetchPath = original.fetchPath; + this.fetchable = original.fetchable; + final Map fetchBuilders; + if ( original.fetchBuilders.isEmpty() ) { + fetchBuilders = Collections.emptyMap(); + } + else { + fetchBuilders = new HashMap<>( original.fetchBuilders.size() ); + for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { + fetchBuilders.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); + } + } + this.fetchBuilders = fetchBuilders; + } + + @Override + public FetchBuilder cacheKeyInstance() { + return new ImplicitFetchBuilderEmbeddable( this ); + } + @Override public Fetch buildFetch( FetchParent parent, @@ -118,6 +141,29 @@ public class ImplicitFetchBuilderEmbeddable implements ImplicitFetchBuilder { return fetch; } + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final ImplicitFetchBuilderEmbeddable that = (ImplicitFetchBuilderEmbeddable) o; + return fetchPath.equals( that.fetchPath ) + && fetchable.equals( that.fetchable ) + && fetchBuilders.equals( that.fetchBuilders ); + } + + @Override + public int hashCode() { + int result = fetchPath.hashCode(); + result = 31 * result + fetchable.hashCode(); + result = 31 * result + fetchBuilders.hashCode(); + return result; + } + @Override public String toString() { return "ImplicitFetchBuilderEmbeddable(" + fetchPath + ")"; diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEntity.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEntity.java index 162ea36079..d99bed93fc 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEntity.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEntity.java @@ -7,6 +7,7 @@ package org.hibernate.query.results.implicit; import java.util.Collections; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.function.BiConsumer; @@ -92,6 +93,27 @@ public class ImplicitFetchBuilderEntity implements ImplicitFetchBuilder { this.fetchBuilders = fetchBuilders; } + private ImplicitFetchBuilderEntity(ImplicitFetchBuilderEntity original) { + this.fetchPath = original.fetchPath; + this.fetchable = original.fetchable; + final Map fetchBuilders; + if ( original.fetchBuilders.isEmpty() ) { + fetchBuilders = Collections.emptyMap(); + } + else { + fetchBuilders = new HashMap<>( original.fetchBuilders.size() ); + for ( Map.Entry entry : original.fetchBuilders.entrySet() ) { + fetchBuilders.put( entry.getKey(), entry.getValue().cacheKeyInstance() ); + } + } + this.fetchBuilders = fetchBuilders; + } + + @Override + public FetchBuilder cacheKeyInstance() { + return new ImplicitFetchBuilderEntity( this ); + } + @Override public Fetch buildFetch( FetchParent parent, @@ -126,6 +148,29 @@ public class ImplicitFetchBuilderEntity implements ImplicitFetchBuilder { fetchBuilders.forEach( (k, v) -> consumer.accept( k.getUnaliasedLocalName(), v ) ); } + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final ImplicitFetchBuilderEntity that = (ImplicitFetchBuilderEntity) o; + return fetchPath.equals( that.fetchPath ) + && fetchable.equals( that.fetchable ) + && fetchBuilders.equals( that.fetchBuilders ); + } + + @Override + public int hashCode() { + int result = fetchPath.hashCode(); + result = 31 * result + fetchable.hashCode(); + result = 31 * result + fetchBuilders.hashCode(); + return result; + } + @Override public String toString() { return "ImplicitFetchBuilderEntity(" + fetchPath + ")"; diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEntityPart.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEntityPart.java new file mode 100644 index 0000000000..5e21d7f966 --- /dev/null +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderEntityPart.java @@ -0,0 +1,80 @@ +/* + * 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.query.results.implicit; + +import java.util.function.BiFunction; + +import org.hibernate.engine.FetchTiming; +import org.hibernate.metamodel.mapping.internal.EntityCollectionPart; +import org.hibernate.query.NavigablePath; +import org.hibernate.query.results.FetchBuilder; +import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; +import org.hibernate.sql.results.graph.DomainResultCreationState; +import org.hibernate.sql.results.graph.Fetch; +import org.hibernate.sql.results.graph.FetchParent; +import org.hibernate.sql.results.jdbc.spi.JdbcValuesMetadata; + +/** + * @author Steve Ebersole + */ +public class ImplicitFetchBuilderEntityPart implements ImplicitFetchBuilder { + private final NavigablePath fetchPath; + private final EntityCollectionPart fetchable; + + public ImplicitFetchBuilderEntityPart(NavigablePath fetchPath, EntityCollectionPart fetchable) { + this.fetchPath = fetchPath; + this.fetchable = fetchable; + } + + @Override + public FetchBuilder cacheKeyInstance() { + return this; + } + + @Override + public Fetch buildFetch( + FetchParent parent, + NavigablePath fetchPath, + JdbcValuesMetadata jdbcResultsMetadata, + BiFunction legacyFetchResolver, + DomainResultCreationState creationState) { + return parent.generateFetchableFetch( + fetchable, + fetchPath, + FetchTiming.IMMEDIATE, + true, + null, + creationState + ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final ImplicitFetchBuilderEntityPart that = (ImplicitFetchBuilderEntityPart) o; + return fetchPath.equals( that.fetchPath ) + && fetchable.equals( that.fetchable ); + } + + @Override + public int hashCode() { + int result = fetchPath.hashCode(); + result = 31 * result + fetchable.hashCode(); + return result; + } + + @Override + public String toString() { + return "ImplicitFetchBuilderEntityPart(" + fetchPath + ")"; + } +} diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderPlural.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderPlural.java index 03b1f103f8..f120ea03ba 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderPlural.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitFetchBuilderPlural.java @@ -12,6 +12,7 @@ import org.hibernate.engine.FetchTiming; import org.hibernate.metamodel.mapping.PluralAttributeMapping; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.FetchBuilder; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; import org.hibernate.sql.results.graph.DomainResultCreationState; import org.hibernate.sql.results.graph.Fetch; @@ -35,6 +36,11 @@ public class ImplicitFetchBuilderPlural implements ImplicitFetchBuilder { this.fetchable = fetchable; } + @Override + public FetchBuilder cacheKeyInstance() { + return this; + } + @Override public Fetch buildFetch( FetchParent parent, @@ -56,6 +62,27 @@ public class ImplicitFetchBuilderPlural implements ImplicitFetchBuilder { return fetch; } + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final ImplicitFetchBuilderPlural that = (ImplicitFetchBuilderPlural) o; + return fetchPath.equals( that.fetchPath ) + && fetchable.equals( that.fetchable ); + } + + @Override + public int hashCode() { + int result = fetchPath.hashCode(); + result = 31 * result + fetchable.hashCode(); + return result; + } + @Override public String toString() { return "ImplicitFetchBuilderPlural(" + fetchPath + ")"; diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderBasic.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderBasic.java index ae2bdfafd7..a4b6a3f3db 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderBasic.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderBasic.java @@ -11,6 +11,7 @@ import java.util.function.BiFunction; import org.hibernate.metamodel.mapping.BasicValuedModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultBuilderBasicValued; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; @@ -37,6 +38,11 @@ public class ImplicitModelPartResultBuilderBasic return modelPart.getExpressableJavaTypeDescriptor().getJavaTypeClass(); } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public BasicResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -50,4 +56,28 @@ public class ImplicitModelPartResultBuilderBasic .getTableGroup( navigablePath.getParent() ); return (BasicResult) modelPart.createDomainResult( navigablePath, tableGroup, null, domainResultCreationState ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + ImplicitModelPartResultBuilderBasic that = (ImplicitModelPartResultBuilderBasic) o; + + if ( !navigablePath.equals( that.navigablePath ) ) { + return false; + } + return modelPart.equals( that.modelPart ); + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + modelPart.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEmbeddable.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEmbeddable.java index 996b825718..4192d10614 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEmbeddable.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEmbeddable.java @@ -11,6 +11,7 @@ import java.util.function.BiFunction; import org.hibernate.metamodel.mapping.EmbeddableValuedModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultBuilderEmbeddable; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; @@ -42,6 +43,11 @@ public class ImplicitModelPartResultBuilderEmbeddable return modelPart.getJavaTypeDescriptor().getJavaTypeClass(); } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public EmbeddableResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -88,4 +94,25 @@ public class ImplicitModelPartResultBuilderEmbeddable ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + final ImplicitModelPartResultBuilderEmbeddable that = (ImplicitModelPartResultBuilderEmbeddable) o; + return navigablePath.equals( that.navigablePath ) + && modelPart.equals( that.modelPart ); + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + modelPart.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEntity.java b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEntity.java index 9c96d7d897..6cfe3ed421 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEntity.java +++ b/hibernate-core/src/main/java/org/hibernate/query/results/implicit/ImplicitModelPartResultBuilderEntity.java @@ -12,6 +12,7 @@ import org.hibernate.metamodel.mapping.EntityMappingType; import org.hibernate.metamodel.mapping.EntityValuedModelPart; import org.hibernate.query.NavigablePath; import org.hibernate.query.results.DomainResultCreationStateImpl; +import org.hibernate.query.results.ResultBuilder; import org.hibernate.query.results.ResultBuilderEntityValued; import org.hibernate.query.results.ResultsHelper; import org.hibernate.query.results.dynamic.DynamicFetchBuilderLegacy; @@ -45,6 +46,11 @@ public class ImplicitModelPartResultBuilderEntity return modelPart.getJavaTypeDescriptor().getJavaTypeClass(); } + @Override + public ResultBuilder cacheKeyInstance() { + return this; + } + @Override public EntityResult buildResult( JdbcValuesMetadata jdbcResultsMetadata, @@ -80,4 +86,28 @@ public class ImplicitModelPartResultBuilderEntity domainResultCreationState ); } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + ImplicitModelPartResultBuilderEntity that = (ImplicitModelPartResultBuilderEntity) o; + + if ( !navigablePath.equals( that.navigablePath ) ) { + return false; + } + return modelPart.equals( that.modelPart ); + } + + @Override + public int hashCode() { + int result = navigablePath.hashCode(); + result = 31 * result + modelPart.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java index 2f6cc739a8..fdbdb80a98 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/internal/NativeQueryImpl.java @@ -398,7 +398,7 @@ public class NativeQueryImpl session.isQueryParametersValidationEnabled() ); - this.resultSetMapping = new ResultSetMappingImpl( sqlString ); + this.resultSetMapping = new ResultSetMappingImpl( sqlString, true ); this.resultMappingSuppliedToCtor = false; } @@ -770,6 +770,7 @@ public class NativeQueryImpl return new SelectInterpretationsKey( getQueryString(), resultSetMapping, + getSynchronizedQuerySpaces(), getQueryOptions().getTupleTransformer(), getQueryOptions().getResultListTransformer() ); diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NonSelectInterpretationsKey.java b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NonSelectInterpretationsKey.java index 304146913e..99a5cdc715 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NonSelectInterpretationsKey.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/NonSelectInterpretationsKey.java @@ -7,8 +7,11 @@ package org.hibernate.query.sql.spi; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import org.hibernate.query.spi.QueryInterpretationCache; +import org.hibernate.query.sqm.internal.SqmInterpretationsKey; /** * QueryInterpretations key for non-select NativeQuery instances @@ -21,11 +24,43 @@ public class NonSelectInterpretationsKey implements QueryInterpretationCache.Key public NonSelectInterpretationsKey(String sql, Collection querySpaces) { this.sql = sql; - this.querySpaces = querySpaces; + this.querySpaces = querySpaces == null ? Collections.emptySet() : querySpaces; } @Override public String getQueryString() { return sql; } + + @Override + public QueryInterpretationCache.Key prepareForStore() { + return new NonSelectInterpretationsKey( + sql, + querySpaces.isEmpty() ? Collections.emptySet() : new HashSet<>( querySpaces ) + ); + } + + @Override + public boolean equals(Object o) { + if ( this == o ) { + return true; + } + if ( o == null || getClass() != o.getClass() ) { + return false; + } + + NonSelectInterpretationsKey that = (NonSelectInterpretationsKey) o; + + if ( !sql.equals( that.sql ) ) { + return false; + } + return querySpaces.equals( that.querySpaces ); + } + + @Override + public int hashCode() { + int result = sql.hashCode(); + result = 31 * result + querySpaces.hashCode(); + return result; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/SelectInterpretationsKey.java b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/SelectInterpretationsKey.java index a31f841edd..eabd1bcb42 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/sql/spi/SelectInterpretationsKey.java +++ b/hibernate-core/src/main/java/org/hibernate/query/sql/spi/SelectInterpretationsKey.java @@ -6,6 +6,8 @@ */ package org.hibernate.query.sql.spi; +import java.util.Collection; +import java.util.HashSet; import java.util.Objects; import org.hibernate.query.ResultListTransformer; @@ -19,18 +21,38 @@ import org.hibernate.sql.results.jdbc.spi.JdbcValuesMappingProducer; public class SelectInterpretationsKey implements QueryInterpretationCache.Key { private final String sql; private final JdbcValuesMappingProducer jdbcValuesMappingProducer; + private final Collection querySpaces; private final TupleTransformer tupleTransformer; private final ResultListTransformer resultListTransformer; + private final int hash; public SelectInterpretationsKey( String sql, JdbcValuesMappingProducer jdbcValuesMappingProducer, + Collection querySpaces, TupleTransformer tupleTransformer, ResultListTransformer resultListTransformer) { this.sql = sql; this.jdbcValuesMappingProducer = jdbcValuesMappingProducer; + this.querySpaces = querySpaces; this.tupleTransformer = tupleTransformer; this.resultListTransformer = resultListTransformer; + this.hash = generateHashCode(); + } + + private SelectInterpretationsKey( + String sql, + JdbcValuesMappingProducer jdbcValuesMappingProducer, + Collection querySpaces, + TupleTransformer tupleTransformer, + ResultListTransformer resultListTransformer, + int hash) { + this.sql = sql; + this.jdbcValuesMappingProducer = jdbcValuesMappingProducer; + this.querySpaces = querySpaces; + this.tupleTransformer = tupleTransformer; + this.resultListTransformer = resultListTransformer; + this.hash = hash; } @Override @@ -38,6 +60,32 @@ public class SelectInterpretationsKey implements QueryInterpretationCache.Key { return sql; } + @Override + public QueryInterpretationCache.Key prepareForStore() { + return new SelectInterpretationsKey( + sql, + jdbcValuesMappingProducer.cacheKeyInstance(), + new HashSet<>( querySpaces ), + tupleTransformer, + resultListTransformer, + hash + ); + } + + private int generateHashCode() { + int result = sql.hashCode(); + result = 31 * result + jdbcValuesMappingProducer.hashCode(); + result = 31 * result + ( querySpaces != null ? querySpaces.hashCode() : 0 ); + result = 31 * result + ( tupleTransformer != null ? tupleTransformer.hashCode() : 0 ); + result = 31 * result + ( resultListTransformer != null ? resultListTransformer.hashCode() : 0 ); + return result; + } + + @Override + public int hashCode() { + return hash; + } + @Override public boolean equals(Object o) { if ( this == o ) { @@ -47,21 +95,11 @@ public class SelectInterpretationsKey implements QueryInterpretationCache.Key { return false; } - SelectInterpretationsKey that = (SelectInterpretationsKey) o; - + final SelectInterpretationsKey that = (SelectInterpretationsKey) o; return sql.equals( that.sql ) && Objects.equals( jdbcValuesMappingProducer, that.jdbcValuesMappingProducer ) + && Objects.equals( querySpaces, that.querySpaces ) && Objects.equals( tupleTransformer, that.tupleTransformer ) && Objects.equals( resultListTransformer, that.resultListTransformer ); - - } - - @Override - public int hashCode() { - int result = sql.hashCode(); - result = 31 * result + jdbcValuesMappingProducer.hashCode(); - result = 31 * result + ( tupleTransformer != null ? tupleTransformer.hashCode() : 0 ); - result = 31 * result + ( resultListTransformer != null ? resultListTransformer.hashCode() : 0 ); - return result; } } diff --git a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMappingProducer.java b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMappingProducer.java index e83fe62ab5..2669feb894 100644 --- a/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMappingProducer.java +++ b/hibernate-core/src/main/java/org/hibernate/sql/results/jdbc/spi/JdbcValuesMappingProducer.java @@ -39,4 +39,7 @@ public interface JdbcValuesMappingProducer { throw new NotYetImplementedFor6Exception( getClass() ); } + default JdbcValuesMappingProducer cacheKeyInstance() { + return this; + } } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java index a84ec73d04..8ab76bc3b0 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/QueryStatisticsImpl.java @@ -202,6 +202,10 @@ public class QueryStatisticsImpl implements QueryStatistics { planCacheHitCount.increment(); } + void incrementPlanCacheMissCount() { + planCacheMissCount.increment(); + } + public String toString() { return "QueryStatistics" + "[query=" + query diff --git a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java index 64c46cab24..b4f7df0170 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/internal/StatisticsImpl.java @@ -767,11 +767,20 @@ public class StatisticsImpl implements StatisticsImplementor, Service { } @Override - public void queryPlanCacheHit(String hql) { + public void queryPlanCacheHit(String query) { queryPlanCacheHitCount.increment(); - if ( hql != null ) { - getQueryStatistics( hql ).incrementPlanCacheHitCount(); + if ( query != null ) { + getQueryStatistics( query ).incrementPlanCacheHitCount(); + } + } + + @Override + public void queryPlanCacheMiss(String query) { + queryPlanCacheMissCount.increment(); + + if ( query != null ) { + getQueryStatistics( query ).incrementPlanCacheMissCount(); } } diff --git a/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java b/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java index efdd6a7cd5..1229412843 100644 --- a/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java +++ b/hibernate-core/src/main/java/org/hibernate/stat/spi/StatisticsImplementor.java @@ -251,9 +251,18 @@ public interface StatisticsImplementor extends Statistics, Service { /** * Callback indicating a get from the query plan cache resulted in a hit. * - * @param hql The query + * @param query The query */ - default void queryPlanCacheHit(String hql) { + default void queryPlanCacheHit(String query) { + //For backward compatibility + } + + /** + * Callback indicating a get from the query plan cache resulted in a miss. + * + * @param query The query + */ + default void queryPlanCacheMiss(String query) { //For backward compatibility } diff --git a/hibernate-core/src/test/java/org/hibernate/orm/test/stat/internal/QueryPlanCacheStatisticsTest.java b/hibernate-core/src/test/java/org/hibernate/orm/test/stat/internal/QueryPlanCacheStatisticsTest.java index c58b8c66fa..87f488a7d4 100644 --- a/hibernate-core/src/test/java/org/hibernate/orm/test/stat/internal/QueryPlanCacheStatisticsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/orm/test/stat/internal/QueryPlanCacheStatisticsTest.java @@ -17,6 +17,9 @@ import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; import org.hibernate.cfg.Environment; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.query.Query; +import org.hibernate.query.spi.QueryImplementor; import org.hibernate.stat.QueryStatistics; import org.hibernate.stat.Statistics; @@ -51,8 +54,6 @@ public class QueryPlanCacheStatisticsTest { @BeforeAll protected void afterEntityManagerFactoryBuilt(SessionFactoryScope scope) { - statistics = scope.getSessionFactory().getStatistics(); - scope.inTransaction( entityManager -> { for ( long i = 1; i <= 5; i++ ) { Employee employee = new Employee(); @@ -64,8 +65,10 @@ public class QueryPlanCacheStatisticsTest { @BeforeEach protected void cleanup(SessionFactoryScope scope) { - scope.getSessionFactory().getQueryEngine().getInterpretationCache().close(); + final SessionFactoryImplementor sessionFactory = scope.getSessionFactory(); + statistics = sessionFactory.getStatistics(); statistics.clear(); + sessionFactory.getQueryEngine().getInterpretationCache().close(); } @Test @@ -125,6 +128,24 @@ public class QueryPlanCacheStatisticsTest { @Test @TestForIssue( jiraKey = "HHH-13077" ) public void testCreateQueryHitCount(SessionFactoryScope scope) { + scope.inTransaction( entityManager -> { + + QueryImplementor query = entityManager.createQuery( + "select e from Employee e", Employee.class ); + + //First time, we get a cache miss, so the query is compiled + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //The hit count should be 0 as we don't need to go to the cache after we already compiled the query + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + + List employees = query.getResultList(); + assertEquals( 5, employees.size() ); + + //The miss count is 2 because the previous cache miss was for the HqlInterpretation and this is for the plan + assertEquals( 2, statistics.getQueryPlanCacheMissCount() ); + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + } ); + scope.inTransaction( entityManager -> { List employees = entityManager.createQuery( @@ -133,6 +154,40 @@ public class QueryPlanCacheStatisticsTest { assertEquals( 5, employees.size() ); + //The miss count is still 2, as now we got the query plan from the cache + assertEquals( 2, statistics.getQueryPlanCacheMissCount() ); + //And the cache hit count increases. + assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); + } ); + + scope.inTransaction( entityManager -> { + + List employees = entityManager.createQuery( + "select e from Employee e", Employee.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 2, as now we got the query plan from the cache + assertEquals( 2, statistics.getQueryPlanCacheMissCount() ); + //And the cache hit count increases. + assertEquals( 4, statistics.getQueryPlanCacheHitCount() ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-14632" ) + public void testCreateNativeQueryHitCount(SessionFactoryScope scope) { + statistics.clear(); + + scope.inTransaction( entityManager -> { + + List employees = entityManager.createNativeQuery( + "select * from employee e", Employee.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + //First time, we get a cache miss, so the query is compiled assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); //The hit count should be 0 as we don't need to go to the cache after we already compiled the query @@ -141,8 +196,8 @@ public class QueryPlanCacheStatisticsTest { scope.inTransaction( entityManager -> { - List employees = entityManager.createQuery( - "select e from Employee e", Employee.class ) + List employees = entityManager.createNativeQuery( + "select * from employee e", Employee.class ) .getResultList(); assertEquals( 5, employees.size() ); @@ -155,8 +210,8 @@ public class QueryPlanCacheStatisticsTest { scope.inTransaction( entityManager -> { - List employees = entityManager.createQuery( - "select e from Employee e", Employee.class ) + List employees = entityManager.createNativeQuery( + "select * from employee e", Employee.class ) .getResultList(); assertEquals( 5, employees.size() ); @@ -176,15 +231,20 @@ public class QueryPlanCacheStatisticsTest { statistics.clear(); scope.inTransaction( entityManager -> { - Employee employees = entityManager.createNamedQuery( - "find_employee_by_name", Employee.class ) - .setParameter( "name", "Employee: 1" ) - .getSingleResult(); + Query query = entityManager.createNamedQuery( + "find_employee_by_name", Employee.class ) + .setParameter( "name", "Employee: 1" ); //The miss count is 0 because the plan was compiled when the EMF was built, and we cleared the Statistics assertEquals( 0, statistics.getQueryPlanCacheMissCount() ); //The hit count is 1 since we got the plan from the cache assertEquals( 1, statistics.getQueryPlanCacheHitCount() ); + + Employee employees = query.getSingleResult(); + + //The miss count is 1 because the previous cache hit was for the HqlInterpretation and this is for the plan + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + assertEquals( 1, statistics.getQueryPlanCacheHitCount() ); } ); scope.inTransaction( entityManager -> { @@ -194,10 +254,11 @@ public class QueryPlanCacheStatisticsTest { .setParameter( "name", "Employee: 1" ) .getSingleResult(); - //The miss count is still 0 because the plan was compiled when the EMF was built, and we cleared the Statistics - assertEquals( 0, statistics.getQueryPlanCacheMissCount() ); - //The hit count is 2 since we got the plan from the cache twice - assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); + //The miss count is still 1 because the plan was compiled when the EMF was built, and we cleared the Statistics + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //The hit count is 3 since we got the plan from the cache twice, + //but the second time we have a hit for the HqlInterpretation and one for the plan + assertEquals( 3, statistics.getQueryPlanCacheHitCount() ); } ); } @@ -206,16 +267,21 @@ public class QueryPlanCacheStatisticsTest { public void testCreateQueryTupleHitCount(SessionFactoryScope scope) { scope.inTransaction( entityManager -> { - List employees = entityManager.createQuery( - "select e.id, e.name from Employee e", Tuple.class ) - .getResultList(); - - assertEquals( 5, employees.size() ); + QueryImplementor query = entityManager.createQuery( + "select e.id, e.name from Employee e", Tuple.class ); //First time, we get a cache miss, so the query is compiled assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); //The hit count should be 0 as we don't need to go to the cache after we already compiled the query assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + + List employees = query.getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is 2 because the previous cache miss was for the HqlInterpretation and this is for the plan + assertEquals( 2, statistics.getQueryPlanCacheMissCount() ); + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); } ); scope.inTransaction( entityManager -> { @@ -226,10 +292,10 @@ public class QueryPlanCacheStatisticsTest { assertEquals( 5, employees.size() ); - //The miss count is still 1, as now we got the query plan from the cache - assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //The miss count is still 2, as now we got the query plan from the cache + assertEquals( 2, statistics.getQueryPlanCacheMissCount() ); //And the cache hit count increases. - assertEquals( 1, statistics.getQueryPlanCacheHitCount() ); + assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); } ); scope.inTransaction( entityManager -> { @@ -240,10 +306,10 @@ public class QueryPlanCacheStatisticsTest { assertEquals( 5, employees.size() ); - //The miss count is still 1, as now we got the query plan from the cache - assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //The miss count is still 2, as now we got the query plan from the cache + assertEquals( 2, statistics.getQueryPlanCacheMissCount() ); //And the cache hit count increases. - assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); + assertEquals( 4, statistics.getQueryPlanCacheHitCount() ); } ); } @@ -253,13 +319,17 @@ public class QueryPlanCacheStatisticsTest { scope.inTransaction( entityManager -> { TypedQuery typedQuery = entityManager.createQuery( "select e from Employee e", Employee.class ); + //First time, we get a cache miss, so the query is compiled + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //The hit count should be 0 as we don't need to go to the cache after we already compiled the query + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + List employees = typedQuery.getResultList(); assertEquals( 5, employees.size() ); - //First time, we get a cache miss, so the query is compiled - assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); - //The hit count should be 0 as we don't need to go to the cache after we already compiled the query + //The miss count is 2 because the previous cache miss was for the HqlInterpretation and this is for the plan + assertEquals( 2, statistics.getQueryPlanCacheMissCount() ); assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); typedQuery.setLockMode( LockModeType.READ ); diff --git a/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java b/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java deleted file mode 100644 index 8b1241cf5b..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * 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.stat.internal; - -import java.util.List; -import java.util.Map; -import jakarta.persistence.Entity; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.Id; -import jakarta.persistence.NamedQuery; -import jakarta.persistence.Table; - -import org.hibernate.SessionFactory; -import org.hibernate.cfg.Environment; -import org.hibernate.orm.test.jpa.BaseEntityManagerFunctionalTestCase; -import org.hibernate.stat.Statistics; - -import org.hibernate.testing.TestForIssue; -import org.junit.Test; - -import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; -import static org.junit.Assert.assertEquals; - -/** - * @author Gail Badner - */ -@TestForIssue(jiraKey = "HHH-12855") -public class QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTestCase { - - private Statistics statistics; - - @Override - public Class[] getAnnotatedClasses() { - return new Class[] { - Employee.class - }; - } - - @Override - protected void addConfigOptions(Map options) { - options.put( Environment.GENERATE_STATISTICS, "true" ); - } - - @Override - protected void afterEntityManagerFactoryBuilt() { - SessionFactory sessionFactory = entityManagerFactory().unwrap( SessionFactory.class ); - statistics = sessionFactory.getStatistics(); - - doInJPA( this::entityManagerFactory, entityManager -> { - for ( long i = 1; i <= 5; i++ ) { - if ( i % 3 == 0 ) { - entityManager.flush(); - } - Employee employee = new Employee(); - employee.setName( String.format( "Employee: %d", i ) ); - entityManager.persist( employee ); - } - } ); - } - - @Test - @TestForIssue( jiraKey = "HHH-14632" ) - public void testCreateNativeQueryHitCount() { - statistics.clear(); - - doInJPA( this::entityManagerFactory, entityManager -> { - - List employees = entityManager.createNativeQuery( - "select * from employee e", Employee.class ) - .getResultList(); - - assertEquals( 5, employees.size() ); - - //First time, we get a cache miss, so the query is compiled - assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); - //The hit count should be 0 as we don't need to go to the cache after we already compiled the query - assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - - List employees = entityManager.createNativeQuery( - "select * from employee e", Employee.class ) - .getResultList(); - - assertEquals( 5, employees.size() ); - - //The miss count is still 1, as now we got the query plan from the cache - assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); - //And the cache hit count increases. - assertEquals( 1, statistics.getQueryPlanCacheHitCount() ); - } ); - - doInJPA( this::entityManagerFactory, entityManager -> { - - List employees = entityManager.createNativeQuery( - "select * from employee e", Employee.class ) - .getResultList(); - - assertEquals( 5, employees.size() ); - - //The miss count is still 1, as now we got the query plan from the cache - assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); - //And the cache hit count increases. - assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); - } ); - } - - @Entity(name = "Employee") - @Table(name = "employee") - @NamedQuery( - name = "find_employee_by_name", - query = "select e from Employee e where e.name = :name" - ) - public static class Employee { - - @Id - @GeneratedValue - private Long id; - - private String name; - - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - } - -}