HHH-16782 Allow query plan caching of criteria queries

This commit is contained in:
Christian Beikov 2023-06-12 17:53:50 +02:00
parent 97a699a3e1
commit d859f43748
24 changed files with 339 additions and 17 deletions

View File

@ -61,6 +61,11 @@ public Boolean isResultCachingEnabled() {
return null;
}
@Override
public Boolean getQueryPlanCachingEnabled() {
return null;
}
@Override
public CacheRetrieveMode getCacheRetrieveMode() {
return null;

View File

@ -201,4 +201,26 @@ public interface HibernateHints {
* @see jakarta.persistence.EntityManager#setProperty(String, Object)
*/
String HINT_JDBC_BATCH_SIZE = "org.hibernate.jdbcBatchSize";
/**
* Hint to enable or disable the query plan caching.
* <p>
* By default, query plan caching is enabled for HQL queries
* and immutable criteria queries i.e. created with {@link org.hibernate.cfg.AvailableSettings#CRITERIA_COPY_TREE}.
* Query plan caching can be disabled for any query by setting this property to {@code false}.
* Query plan caching can be enabled for mutable criteria queries by setting this property to {@code true}.
* <p>
* Setting this property to {@code true} for mutable criteria queries can lead to cache trashing,
* because the query plan is cached based on a copy of the criteria query.
* This is mostly useful when the same {@link org.hibernate.query.Query} should be executed multiple times,
* but with different parameter values to avoid re-translation of the criteria query.
* <p>
* Note that setting this property to {@code true} does not override the basic safety measures of Hibernate.
* Hibernate will never cache query plans that are not safe to cache, regardless of the value of this property.
*
* @see org.hibernate.query.SelectionQuery#setQueryPlanCacheable
*
* @since 6.3
*/
String HINT_QUERY_PLAN_CACHEABLE = "hibernate.query.plan.cacheable";
}

View File

@ -257,6 +257,11 @@ public Boolean isResultCachingEnabled() {
return false;
}
@Override
public Boolean getQueryPlanCachingEnabled() {
return null;
}
@Override
public CacheRetrieveMode getCacheRetrieveMode() {
return CacheRetrieveMode.BYPASS;

View File

@ -403,6 +403,18 @@ default Stream<R> stream() {
*/
SelectionQuery<R> setCacheable(boolean cacheable);
/**
* Should the query plan of the query be stored in the query plan cache?
*/
boolean isQueryPlanCacheable();
/**
* Enable/disable query plan caching for this query.
*
* @see #isQueryPlanCacheable
*/
SelectionQuery<R> setQueryPlanCacheable(boolean queryPlanCacheable);
/**
* Obtain the name of the second level query cache region in which query
* results will be stored (if they are cached, see the discussion on

View File

@ -40,6 +40,7 @@ public class QueryOptionsImpl implements MutableQueryOptions, AppliedGraph {
private Boolean resultCachingEnabled;
private String resultCacheRegionName;
private Boolean readOnlyEnabled;
private Boolean queryPlanCachingEnabled;
private TupleTransformer tupleTransformer;
private ResultListTransformer resultListTransformer;
@ -61,6 +62,7 @@ public FlushMode getFlushMode() {
return flushMode;
}
@Override
public void setFlushMode(FlushMode flushMode) {
this.flushMode = flushMode;
}
@ -70,6 +72,7 @@ public String getComment() {
return comment;
}
@Override
public void setComment(String comment) {
this.comment = comment;
}
@ -141,6 +144,7 @@ public Boolean isResultCachingEnabled() {
return resultCachingEnabled;
}
@Override
public void setResultCachingEnabled(boolean resultCachingEnabled) {
this.resultCachingEnabled = resultCachingEnabled;
}
@ -150,6 +154,16 @@ public String getResultCacheRegionName() {
return resultCacheRegionName;
}
@Override
public Boolean getQueryPlanCachingEnabled() {
return queryPlanCachingEnabled;
}
@Override
public void setQueryPlanCachingEnabled(Boolean queryPlanCachingEnabled) {
this.queryPlanCachingEnabled = queryPlanCachingEnabled;
}
@Override
public TupleTransformer getTupleTransformer() {
return tupleTransformer;
@ -160,6 +174,7 @@ public ResultListTransformer getResultListTransformer() {
return resultListTransformer;
}
@Override
public void setResultCacheRegionName(String resultCacheRegionName) {
this.resultCacheRegionName = resultCacheRegionName;
}

View File

@ -71,6 +71,7 @@
import static org.hibernate.jpa.HibernateHints.HINT_FLUSH_MODE;
import static org.hibernate.jpa.HibernateHints.HINT_FOLLOW_ON_LOCKING;
import static org.hibernate.jpa.HibernateHints.HINT_NATIVE_SPACES;
import static org.hibernate.jpa.HibernateHints.HINT_QUERY_PLAN_CACHEABLE;
import static org.hibernate.jpa.HibernateHints.HINT_TIMEOUT;
import static org.hibernate.jpa.LegacySpecHints.HINT_JAVAEE_CACHE_RETRIEVE_MODE;
import static org.hibernate.jpa.LegacySpecHints.HINT_JAVAEE_CACHE_STORE_MODE;
@ -182,6 +183,7 @@ protected void collectHints(Map<String, Object> hints) {
putIfNotNull( hints, HINT_CACHEABLE, getQueryOptions().isResultCachingEnabled() );
putIfNotNull( hints, HINT_CACHE_REGION, getQueryOptions().getResultCacheRegionName() );
putIfNotNull( hints, HINT_CACHE_MODE, getQueryOptions().getCacheMode() );
putIfNotNull( hints, HINT_QUERY_PLAN_CACHEABLE, getQueryOptions().getQueryPlanCachingEnabled() );
putIfNotNull( hints, HINT_SPEC_CACHE_RETRIEVE_MODE, getQueryOptions().getCacheRetrieveMode() );
putIfNotNull( hints, HINT_JAVAEE_CACHE_RETRIEVE_MODE, getQueryOptions().getCacheRetrieveMode() );
@ -299,6 +301,9 @@ protected final boolean applySelectionHint(String hintName, Object value) {
case HINT_FETCH_SIZE:
applyFetchSizeHint( getInteger( value ) );
return true;
case HINT_QUERY_PLAN_CACHEABLE:
applyQueryPlanCacheableHint( getBoolean( value ) );
return true;
case HINT_CACHEABLE:
applyCacheableHint( getBoolean( value ) );
return true;
@ -343,6 +348,10 @@ protected void applyFetchSizeHint(int fetchSize) {
getQueryOptions().setFetchSize( fetchSize );
}
protected void applyQueryPlanCacheableHint(boolean isCacheable) {
getQueryOptions().setQueryPlanCachingEnabled( isCacheable );
}
protected void applyCacheModeHint(CacheMode cacheMode) {
getQueryOptions().setCacheMode( cacheMode );
}

View File

@ -37,6 +37,7 @@
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.QueryParameter;
import org.hibernate.query.ResultListTransformer;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.TupleTransformer;
import org.hibernate.query.named.NamedQueryMemento;
import org.hibernate.query.sqm.SqmExpressible;
@ -221,6 +222,12 @@ public QueryImplementor<R> setCacheRegion(String cacheRegion) {
return this;
}
@Override
public QueryImplementor<R> setQueryPlanCacheable(boolean queryPlanCacheable) {
super.setQueryPlanCacheable( queryPlanCacheable );
return this;
}
@Override
public QueryImplementor<R> setTimeout(int timeout) {
super.setTimeout( timeout );

View File

@ -804,6 +804,18 @@ public SelectionQuery<R> setCacheable(boolean cacheable) {
return this;
}
@Override
public boolean isQueryPlanCacheable() {
// By default, we assume query plan caching is enabled unless explicitly disabled
return getQueryOptions().getQueryPlanCachingEnabled() != Boolean.FALSE;
}
@Override
public SelectionQuery<R> setQueryPlanCacheable(boolean queryPlanCacheable) {
getQueryOptions().setQueryPlanCachingEnabled( queryPlanCacheable );
return this;
}
@Override
public String getCacheRegion() {
return getQueryOptions().getResultCacheRegionName();

View File

@ -74,6 +74,11 @@ public CacheStoreMode getCacheStoreMode() {
return queryOptions.getCacheStoreMode();
}
@Override
public Boolean getQueryPlanCachingEnabled() {
return queryOptions.getQueryPlanCachingEnabled();
}
@Override
public CacheMode getCacheMode() {
return queryOptions.getCacheMode();

View File

@ -59,6 +59,11 @@ default void setCacheMode(CacheMode cacheMode) {
*/
void setResultCacheRegionName(String cacheRegion);
/**
* Corollary to {@link #getQueryPlanCachingEnabled()}
*/
void setQueryPlanCachingEnabled(Boolean queryPlanCachingEnabled);
/**
* Corollary to {@link #getTimeout()}
*/

View File

@ -99,6 +99,11 @@ default CacheMode getCacheMode() {
*/
String getResultCacheRegionName();
/**
* Should the query plan of the query be cached?
*/
Boolean getQueryPlanCachingEnabled();
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// JDBC / SQL options

View File

@ -80,6 +80,11 @@ public Boolean isResultCachingEnabled() {
return null;
}
@Override
public Boolean getQueryPlanCachingEnabled() {
return null;
}
@Override
public String getResultCacheRegionName() {
return null;

View File

@ -1149,6 +1149,12 @@ public NativeQueryImplementor<R> setCacheRegion(String cacheRegion) {
return this;
}
@Override
public NativeQueryImplementor<R> setQueryPlanCacheable(boolean queryPlanCacheable) {
super.setQueryPlanCacheable( queryPlanCacheable );
return this;
}
@Override
public NativeQueryImplementor<R> setTimeout(int timeout) {
super.setTimeout( timeout );

View File

@ -227,6 +227,8 @@ public QuerySqmImpl(
}
else {
this.sqm = criteria;
// Cache immutable query plans by default
setQueryPlanCacheable( true );
}
setComment( hql );
@ -436,7 +438,8 @@ public String getQueryString() {
return hql;
}
public SqmStatement getSqmStatement() {
@Override
public SqmStatement<R> getSqmStatement() {
return sqm;
}
@ -633,6 +636,14 @@ protected ScrollableResultsImplementor doScroll(ScrollMode scrollMode) {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Select query plan
@Override
public boolean isQueryPlanCacheable() {
return CRITERIA_HQL_STRING.equals( hql )
// For criteria queries, query plan caching requires an explicit opt-in
? getQueryOptions().getQueryPlanCachingEnabled() == Boolean.TRUE
: super.isQueryPlanCacheable();
}
private SelectQueryPlan<R> resolveSelectQueryPlan() {
final QueryInterpretationCache.Key cacheKey = SqmInterpretationsKey.createInterpretationsKey( this );
if ( cacheKey != null ) {
@ -1202,6 +1213,12 @@ public SqmQueryImplementor<R> setCacheRegion(String cacheRegion) {
return this;
}
@Override
public SqmQueryImplementor<R> setQueryPlanCacheable(boolean queryPlanCacheable) {
super.setQueryPlanCacheable( queryPlanCacheable );
return this;
}
@Override
public SqmQueryImplementor<R> setTimeout(int timeout) {
super.setTimeout( timeout );

View File

@ -16,6 +16,7 @@
import org.hibernate.query.TupleTransformer;
import org.hibernate.query.spi.QueryInterpretationCache;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.tree.SqmStatement;
import static java.lang.Boolean.TRUE;
import static org.hibernate.query.spi.AbstractSelectionQuery.CRITERIA_HQL_STRING;
@ -25,7 +26,9 @@
*/
public class SqmInterpretationsKey implements QueryInterpretationCache.Key {
public interface CacheabilityInfluencers {
boolean isQueryPlanCacheable();
String getQueryString();
SqmStatement<?> getSqmStatement();
QueryOptions getQueryOptions();
LoadQueryInfluencers getLoadQueryInfluencers();
Supplier<Boolean> hasMultiValuedParameterBindingsChecker();
@ -36,9 +39,11 @@ public interface InterpretationsKeySource extends CacheabilityInfluencers {
}
public static SqmInterpretationsKey createInterpretationsKey(InterpretationsKeySource keySource) {
if ( isCacheable (keySource ) ) {
if ( isCacheable ( keySource ) ) {
return new SqmInterpretationsKey(
keySource.getQueryString(),
CRITERIA_HQL_STRING.equals( keySource.getQueryString() )
? keySource.getSqmStatement()
: keySource.getQueryString(),
keySource.getResultType(),
keySource.getQueryOptions().getLockOptions(),
keySource.getQueryOptions().getTupleTransformer(),
@ -57,7 +62,7 @@ private static boolean isCacheable(InterpretationsKeySource keySource) {
// for now at least, skip caching Criteria-based plans
// - especially wrt parameters atm; this works with HQL because the
// parameters are part of the query string; with Criteria, they're not.
return ! CRITERIA_HQL_STRING.equals( keySource.getQueryString() )
return keySource.isQueryPlanCacheable()
// At the moment we cannot cache query plan if there is filter enabled.
&& ! keySource.getLoadQueryInfluencers().hasEnabledFilters()
// At the moment we cannot cache query plan if it has an entity graph
@ -79,7 +84,7 @@ public static QueryInterpretationCache.Key generateNonSelectKey(InterpretationsK
return null;
}
private final String query;
private final Object query;
private final Class<?> resultType;
private final LockOptions lockOptions;
private final TupleTransformer<?> tupleTransformer;
@ -87,7 +92,7 @@ public static QueryInterpretationCache.Key generateNonSelectKey(InterpretationsK
private final Collection<String> enabledFetchProfiles;
private SqmInterpretationsKey(
String query,
Object query,
Class<?> resultType,
LockOptions lockOptions,
TupleTransformer<?> tupleTransformer,
@ -116,7 +121,7 @@ public QueryInterpretationCache.Key prepareForStore() {
@Override
public String getQueryString() {
return query;
return query instanceof String ? (String) query : null;
}
@Override

View File

@ -54,6 +54,7 @@
import org.hibernate.query.spi.SelectQueryPlan;
import org.hibernate.query.sqm.SqmSelectionQuery;
import org.hibernate.query.sqm.internal.SqmInterpretationsKey.InterpretationsKeySource;
import org.hibernate.query.sqm.tree.SqmCopyContext;
import org.hibernate.query.sqm.tree.expression.JpaCriteriaParameter;
import org.hibernate.query.sqm.tree.expression.SqmJpaCriteriaParameterWrapper;
import org.hibernate.query.sqm.tree.expression.SqmParameter;
@ -195,7 +196,14 @@ public SqmSelectionQueryImpl(
SharedSessionContractImplementor session) {
super( session );
this.hql = CRITERIA_HQL_STRING;
this.sqm = session.isCriteriaCopyTreeEnabled() ? criteria.copy( simpleContext() ) : criteria;
if ( session.isCriteriaCopyTreeEnabled() ) {
this.sqm = criteria.copy( SqmCopyContext.simpleContext() );
}
else {
this.sqm = criteria;
// Cache immutable query plans by default
setQueryPlanCacheable( true );
}
this.domainParameterXref = DomainParameterXref.from( sqm );
this.parameterMetadata = domainParameterXref.hasParameters()
@ -233,8 +241,8 @@ public TupleMetadata getTupleMetadata() {
return tupleMetadata;
}
@SuppressWarnings("rawtypes")
public SqmSelectStatement getSqmStatement() {
@Override
public SqmSelectStatement<R> getSqmStatement() {
return sqm;
}
@ -524,6 +532,20 @@ public SqmSelectionQuery<R> setCacheRegion(String regionName) {
return this;
}
@Override
public SqmSelectionQuery<R> setQueryPlanCacheable(boolean queryPlanCacheable) {
super.setQueryPlanCacheable( queryPlanCacheable );
return this;
}
@Override
public boolean isQueryPlanCacheable() {
return CRITERIA_HQL_STRING.equals( hql )
// For criteria queries, query plan caching requires an explicit opt-in
? getQueryOptions().getQueryPlanCachingEnabled() == Boolean.TRUE
: super.isQueryPlanCacheable();
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// hints

View File

@ -743,6 +743,11 @@ public Boolean isResultCachingEnabled() {
return resultCachingEnabled;
}
@Override
public Boolean getQueryPlanCachingEnabled() {
return null;
}
@Override
public CacheRetrieveMode getCacheRetrieveMode() {
return cacheRetrieveMode;

View File

@ -9,6 +9,7 @@
import java.util.Comparator;
import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.internal.util.ExceptionHelper;
import jakarta.persistence.Column;
@ -32,8 +33,10 @@
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -58,6 +61,7 @@
}
)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = AvailableSettings.CRITERIA_COPY_TREE, value = "true"))
public class TransientOverrideAsPersistentJoined {
@Test

View File

@ -7,6 +7,9 @@
package org.hibernate.orm.test.inheritance;
import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.DiscriminatorColumn;
@ -29,8 +32,10 @@
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -53,6 +58,7 @@
}
)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = AvailableSettings.CRITERIA_COPY_TREE, value = "true"))
public class TransientOverrideAsPersistentMappedSuperclass {
@Test

View File

@ -7,6 +7,9 @@
package org.hibernate.orm.test.inheritance;
import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.DiscriminatorColumn;
@ -28,8 +31,10 @@
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -51,6 +56,7 @@
}
)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = AvailableSettings.CRITERIA_COPY_TREE, value = "true"))
public class TransientOverrideAsPersistentSingleTable {
@Test

View File

@ -8,6 +8,9 @@
import java.util.Comparator;
import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
import jakarta.persistence.DiscriminatorColumn;
@ -29,8 +32,10 @@
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -52,6 +57,7 @@
}
)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = AvailableSettings.CRITERIA_COPY_TREE, value = "true"))
public class TransientOverrideAsPersistentTablePerClass {
@Test

View File

@ -8,10 +8,14 @@
import java.util.List;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -54,6 +58,7 @@
}
)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = AvailableSettings.CRITERIA_COPY_TREE, value = "true"))
public class TransientOverrideAsPersistentWithEmbeddable {
@Test

View File

@ -10,6 +10,7 @@
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Root;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.query.Query;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
@ -19,6 +20,7 @@
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@ -28,11 +30,11 @@
/**
* @author Jan Schatteman
*/
@ServiceRegistry
@DomainModel(
standardModels = StandardDomainModel.GAMBIT
)
@SessionFactory
@ServiceRegistry(settings = @Setting(name = AvailableSettings.CRITERIA_COPY_TREE, value = "true"))
public class ILikeCriteriaTest {
@BeforeEach

View File

@ -16,15 +16,20 @@
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import org.hibernate.cfg.AvailableSettings;
import org.hibernate.cfg.Environment;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.jpa.AvailableHints;
import org.hibernate.query.Query;
import org.hibernate.query.criteria.HibernateCriteriaBuilder;
import org.hibernate.query.criteria.JpaCriteriaQuery;
import org.hibernate.query.spi.QueryImplementor;
import org.hibernate.stat.QueryStatistics;
import org.hibernate.stat.Statistics;
import org.hibernate.testing.TestForIssue;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.JiraKey;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
@ -47,7 +52,7 @@
@Setting( name = Environment.GENERATE_STATISTICS, value = "true")
})
@SessionFactory
@TestForIssue(jiraKey = "HHH-12855")
@JiraKey("HHH-12855")
public class QueryPlanCacheStatisticsTest {
private Statistics statistics;
@ -126,7 +131,7 @@ public void test(SessionFactoryScope scope) {
}
@Test
@TestForIssue( jiraKey = "HHH-13077" )
@JiraKey("HHH-13077")
public void testCreateQueryHitCount(SessionFactoryScope scope) {
scope.inTransaction( entityManager -> {
@ -176,7 +181,7 @@ public void testCreateQueryHitCount(SessionFactoryScope scope) {
}
@Test
@TestForIssue( jiraKey = "HHH-14632" )
@JiraKey("HHH-14632")
public void testCreateNativeQueryHitCount(SessionFactoryScope scope) {
statistics.clear();
@ -224,7 +229,7 @@ public void testCreateNativeQueryHitCount(SessionFactoryScope scope) {
}
@Test
@TestForIssue( jiraKey = "HHH-13077" )
@JiraKey("HHH-13077")
public void testCreateNamedQueryHitCount(SessionFactoryScope scope) {
// Compile the named queries
scope.getSessionFactory().getQueryEngine().getNamedObjectRepository().checkNamedQueries( scope.getSessionFactory().getQueryEngine() );
@ -263,7 +268,7 @@ public void testCreateNamedQueryHitCount(SessionFactoryScope scope) {
}
@Test
@TestForIssue( jiraKey = "HHH-13077" )
@JiraKey("HHH-13077")
public void testCreateQueryTupleHitCount(SessionFactoryScope scope) {
scope.inTransaction( entityManager -> {
@ -314,7 +319,7 @@ public void testCreateQueryTupleHitCount(SessionFactoryScope scope) {
}
@Test
@TestForIssue(jiraKey = "HHH-13077")
@JiraKey("HHH-13077")
public void testLockModeHitCount(SessionFactoryScope scope) {
scope.inTransaction( entityManager -> {
TypedQuery<Employee> typedQuery = entityManager.createQuery( "select e from Employee e", Employee.class );
@ -344,6 +349,132 @@ public void testLockModeHitCount(SessionFactoryScope scope) {
} );
}
@Test
@JiraKey("HHH-16782")
public void testCriteriaQuery(SessionFactoryScope scope) {
scope.inTransaction( entityManager -> {
HibernateCriteriaBuilder cb = entityManager.getCriteriaBuilder();
JpaCriteriaQuery<Employee> cq = cb.createQuery( Employee.class );
cq.from( Employee.class );
entityManager.setProperty( AvailableSettings.CRITERIA_COPY_TREE, true );
TypedQuery<Employee> typedQuery = entityManager.createQuery( cq );
// Criteria query does not need parsing, so no miss or hit at this point
assertEquals( 0, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 0, statistics.getQueryExecutionCount() );
List<Employee> employees = typedQuery.getResultList();
assertEquals( 5, employees.size() );
// The miss count is 0 because the query plan is not even considered for query plan caching
assertEquals( 0, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 1, statistics.getQueryExecutionCount() );
typedQuery.getResultList();
// The miss count is 0 because the query plan is not even considered for query plan caching
assertEquals( 0, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 2, statistics.getQueryExecutionCount() );
} );
}
@Test
@JiraKey("HHH-16782")
public void testCriteriaQueryCache(SessionFactoryScope scope) {
scope.inTransaction( entityManager -> {
HibernateCriteriaBuilder cb = entityManager.getCriteriaBuilder();
JpaCriteriaQuery<Employee> cq = cb.createQuery( Employee.class );
cq.from( Employee.class );
TypedQuery<Employee> typedQuery = entityManager.createQuery( cq );
typedQuery.setHint( AvailableHints.HINT_QUERY_PLAN_CACHEABLE, true );
// Criteria query does not need parsing, so no miss or hit at this point
assertEquals( 0, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 0, statistics.getQueryExecutionCount() );
List<Employee> employees = typedQuery.getResultList();
assertEquals( 5, employees.size() );
// The miss count is 1 because the query plan is resolved once
assertEquals( 1, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 1, statistics.getQueryExecutionCount() );
typedQuery.getResultList();
// The hit count should increase on second access though
assertEquals( 1, statistics.getQueryPlanCacheMissCount() );
assertEquals( 1, statistics.getQueryPlanCacheHitCount() );
assertEquals( 2, statistics.getQueryExecutionCount() );
} );
}
@Test
@JiraKey("HHH-16782")
public void testCriteriaQueryNoCopyTree(SessionFactoryScope scope) {
scope.inTransaction( entityManager -> {
HibernateCriteriaBuilder cb = entityManager.getCriteriaBuilder();
JpaCriteriaQuery<Employee> cq = cb.createQuery( Employee.class );
cq.from( Employee.class );
entityManager.setProperty( AvailableSettings.CRITERIA_COPY_TREE, false );
TypedQuery<Employee> typedQuery = entityManager.createQuery( cq );
// Criteria query does not need parsing, so no miss or hit at this point
assertEquals( 0, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 0, statistics.getQueryExecutionCount() );
List<Employee> employees = typedQuery.getResultList();
assertEquals( 5, employees.size() );
// The miss count is 1 because the query plan is resolved once
assertEquals( 1, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 1, statistics.getQueryExecutionCount() );
typedQuery.getResultList();
// The hit count should increase on second access though
assertEquals( 1, statistics.getQueryPlanCacheMissCount() );
assertEquals( 1, statistics.getQueryPlanCacheHitCount() );
assertEquals( 2, statistics.getQueryExecutionCount() );
} );
}
@Test
@JiraKey("HHH-16782")
public void testDisableQueryPlanCache(SessionFactoryScope scope) {
scope.inTransaction( entityManager -> {
TypedQuery<Employee> typedQuery = entityManager.createQuery( "select e from Employee e", Employee.class );
typedQuery.setHint( AvailableHints.HINT_QUERY_PLAN_CACHEABLE, false );
//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<Employee> employees = typedQuery.getResultList();
assertEquals( 5, employees.size() );
//The miss count is still 1 and hit count still 0 because plan is not even considered for caching
assertEquals( 1, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 1, statistics.getQueryExecutionCount() );
typedQuery.getResultList();
//The miss count is still 1 and hit count still 0 because plan is not even considered for caching
assertEquals( 1, statistics.getQueryPlanCacheMissCount() );
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 2, statistics.getQueryExecutionCount() );
} );
}
private void assertQueryStatistics(String hql, int hitCount) {
QueryStatistics queryStatistics = statistics.getQueryStatistics( hql );