diff --git a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java index 2fd16f8f8f..e271a367d9 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -638,11 +638,11 @@ public QueryImplementor getNamedQuery(String name) { throw getExceptionConverter().convert( new IllegalArgumentException( "No query defined for that name [" + name + "]" ) ); } - protected QueryImplementor createQuery(NamedQueryDefinition queryDefinition) { + protected QueryImpl createQuery(NamedQueryDefinition queryDefinition) { String queryString = queryDefinition.getQueryString(); final QueryImpl query = new QueryImpl( this, - getQueryPlan( queryString, false ).getParameterMetadata(), + getQueryPlan( queryString, false ), queryString ); applyQuerySettingsAndHints( query ); @@ -714,7 +714,7 @@ protected void initQueryFromNamedDefinition(Query query, NamedQueryDefinition nq } @Override - public QueryImplementor createQuery(String queryString) { + public QueryImpl createQuery(String queryString) { checkOpen(); pulseTransactionCoordinator(); delayedAfterCompletion(); @@ -722,7 +722,7 @@ public QueryImplementor createQuery(String queryString) { try { final QueryImpl query = new QueryImpl( this, - getQueryPlan( queryString, false ).getParameterMetadata(), + getQueryPlan( queryString, false ), queryString ); applyQuerySettingsAndHints( query ); @@ -822,7 +822,7 @@ public QueryImplementor createQuery(String queryString, Class resultCl try { // do the translation - final QueryImplementor query = createQuery( queryString ); + final QueryImpl query = createQuery( queryString ); resultClassChecking( resultClass, query ); return query; } @@ -832,13 +832,10 @@ public QueryImplementor createQuery(String queryString, Class resultCl } @SuppressWarnings({"unchecked", "WeakerAccess", "StatementWithEmptyBody"}) - protected void resultClassChecking(Class resultClass, org.hibernate.Query hqlQuery) { + protected void resultClassChecking(Class resultClass, QueryImpl hqlQuery) { // make sure the query is a select -> HHH-7192 - final HQLQueryPlan queryPlan = getFactory().getQueryPlanCache().getHQLQueryPlan( - hqlQuery.getQueryString(), - false, - getLoadQueryInfluencers().getEnabledFilters() - ); + HQLQueryPlan queryPlan = hqlQuery.getQueryPlan(); + if ( queryPlan.getTranslators()[0].isManipulationStatement() ) { throw new IllegalArgumentException( "Update/delete queries cannot be typed" ); } @@ -914,7 +911,7 @@ protected QueryImplementor buildQueryFromName(String name, Class resu @SuppressWarnings({"WeakerAccess", "unchecked"}) protected QueryImplementor createQuery(NamedQueryDefinition namedQueryDefinition, Class resultType) { - final QueryImplementor query = createQuery( namedQueryDefinition ); + final QueryImpl query = createQuery( namedQueryDefinition ); if ( resultType != null ) { resultClassChecking( resultType, query ); } diff --git a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java index 0a0396681f..39f6a4914c 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/SessionImpl.java @@ -1523,7 +1523,7 @@ public Iterator iterate(String query, QueryParameters queryParameters) throws Hi queryParameters.validateParameters(); HQLQueryPlan plan = queryParameters.getQueryPlan(); - if ( plan == null ) { + if ( plan == null || !plan.isShallow() ) { plan = getQueryPlan( query, true ); } diff --git a/hibernate-core/src/main/java/org/hibernate/query/Query.java b/hibernate-core/src/main/java/org/hibernate/query/Query.java index c56e1ea1a0..9dfc5f77a8 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/Query.java +++ b/hibernate-core/src/main/java/org/hibernate/query/Query.java @@ -67,7 +67,7 @@ * @author Steve Ebersole * @author Gavin King */ -@SuppressWarnings("UnusedDeclaration") +@SuppressWarnings("UnusedDeclaratiqon") public interface Query extends TypedQuery, org.hibernate.Query, CommonQueryContract { /** * Get the QueryProducer this Query originates from. diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java index 87a28dfbea..1ca1739101 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/AbstractProducedQuery.java @@ -1434,8 +1434,8 @@ protected QueryParameters makeQueryParametersForExecution(String hql) { ); } - QueryParameters queryParameters = new QueryParameters( - getQueryParameterBindings(), + QueryParameters queryParameters = new QueryParameters( + getQueryParameterBindings(), getLockOptions(), queryOptions, true, @@ -1450,13 +1450,24 @@ protected QueryParameters makeQueryParametersForExecution(String hql) { optionalId, resultTransformer ); - queryParameters.setQueryPlan( entityGraphHintedQueryPlan ); + + appendQueryPlanToQueryParameters( hql, queryParameters, entityGraphHintedQueryPlan ); + if ( passDistinctThrough != null ) { queryParameters.setPassDistinctThrough( passDistinctThrough ); } return queryParameters; } + protected void appendQueryPlanToQueryParameters( + String hql, + QueryParameters queryParameters, + HQLQueryPlan queryPlan) { + if ( queryPlan != null ) { + queryParameters.setQueryPlan( queryPlan ); + } + } + public QueryParameters getQueryParameters() { final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); return makeQueryParametersForExecution( expandedQuery ); @@ -1732,7 +1743,7 @@ public Type determineProperBooleanType(int position, Object value, Type defaultT : defaultType; } - private boolean isSelect() { + protected boolean isSelect() { return getProducer().getFactory().getQueryPlanCache() .getHQLQueryPlan( getQueryString(), false, Collections.emptyMap() ) .isSelect(); diff --git a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java index cda7388a8c..35d8ad55dd 100644 --- a/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java +++ b/hibernate-core/src/main/java/org/hibernate/query/internal/QueryImpl.java @@ -6,8 +6,10 @@ */ package org.hibernate.query.internal; +import org.hibernate.engine.query.spi.HQLQueryPlan; +import org.hibernate.engine.query.spi.ReturnMetadata; +import org.hibernate.engine.spi.QueryParameters; import org.hibernate.engine.spi.SharedSessionContractImplementor; -import org.hibernate.query.ParameterMetadata; import org.hibernate.query.Query; import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.type.Type; @@ -18,16 +20,19 @@ public class QueryImpl extends AbstractProducedQuery implements Query { private final String queryString; + private final HQLQueryPlan hqlQueryPlan; + private final QueryParameterBindingsImpl queryParameterBindings; public QueryImpl( SharedSessionContractImplementor producer, - ParameterMetadata parameterMetadata, + HQLQueryPlan hqlQueryPlan, String queryString) { - super( producer, parameterMetadata ); + super( producer, hqlQueryPlan.getParameterMetadata() ); + this.hqlQueryPlan = hqlQueryPlan; this.queryString = queryString; this.queryParameterBindings = QueryParameterBindingsImpl.from( - parameterMetadata, + hqlQueryPlan.getParameterMetadata(), producer.getFactory(), producer.isQueryParametersValidationEnabled() ); @@ -43,6 +48,10 @@ public String getQueryString() { return queryString; } + public HQLQueryPlan getQueryPlan() { + return hqlQueryPlan; + } + @Override protected boolean isNativeQuery() { return false; @@ -50,12 +59,14 @@ protected boolean isNativeQuery() { @Override public Type[] getReturnTypes() { - return getProducer().getFactory().getReturnTypes( queryString ); + final ReturnMetadata metadata = hqlQueryPlan.getReturnMetadata(); + return metadata == null ? null : metadata.getReturnTypes(); } @Override public String[] getReturnAliases() { - return getProducer().getFactory().getReturnAliases( queryString ); + final ReturnMetadata metadata = hqlQueryPlan.getReturnMetadata(); + return metadata == null ? null : metadata.getReturnAliases(); } @Override @@ -67,4 +78,22 @@ public Query setEntity(int position, Object val) { public Query setEntity(String name, Object val) { return setParameter( name, val, getProducer().getFactory().getTypeHelper().entity( resolveEntityName( val ) ) ); } + + @Override + protected boolean isSelect() { + return hqlQueryPlan.isSelect(); + } + + @Override + protected void appendQueryPlanToQueryParameters( + String hql, + QueryParameters queryParameters, + HQLQueryPlan queryPlan) { + if ( queryPlan != null ) { + queryParameters.setQueryPlan( queryPlan ); + } + else if ( hql.equals( getQueryString() ) ) { + queryParameters.setQueryPlan( getQueryPlan() ); + } + } } 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 index a0a02b1705..5974b976b0 100644 --- a/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java +++ b/hibernate-core/src/test/java/org/hibernate/stat/internal/QueryPlanCacheStatisticsTest.java @@ -6,22 +6,28 @@ */ package org.hibernate.stat.internal; +import java.util.List; import java.util.Map; + import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.LockModeType; +import javax.persistence.NamedQuery; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; import org.hibernate.SessionFactory; import org.hibernate.cfg.Environment; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.stat.QueryStatistics; 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; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** @@ -39,6 +45,7 @@ public Class[] getAnnotatedClasses() { }; } + @Override protected void addConfigOptions(Map options) { options.put( Environment.GENERATE_STATISTICS, "true" ); } @@ -63,6 +70,7 @@ protected void afterEntityManagerFactoryBuilt() { @Test public void test() { + statistics.clear(); assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); assertEquals( 0, statistics.getQueryPlanCacheMissCount() ); @@ -115,6 +123,165 @@ public void test() { } ); } + @Test + @TestForIssue( jiraKey = "HHH-13077" ) + public void testCreateQueryHitCount() { + statistics.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + + List employees = entityManager.createQuery( + "select e 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.createQuery( + "select e from Employee e", Employee.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 1, as no 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.createQuery( + "select e from Employee e", Employee.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 1, as no we got the query plan from the cache + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //And the cache hit count increases. + assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13077" ) + public void testCreateNamedQueryHitCount() { + //This is for the NamedQuery that gets compiled + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + statistics.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + + Employee employees = entityManager.createNamedQuery( + "find_employee_by_name", Employee.class ) + .setParameter( "name", "Employee: 1" ) + .getSingleResult(); + + //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() ); + } ); + + doInJPA( this::entityManagerFactory, entityManager -> { + + Employee employees = entityManager.createNamedQuery( + "find_employee_by_name", Employee.class ) + .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() ); + } ); + } + + @Test + @TestForIssue( jiraKey = "HHH-13077" ) + public void testCreateQueryTupleHitCount() { + statistics.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + + List employees = entityManager.createQuery( + "select e.id, e.name from Employee e", Tuple.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.createQuery( + "select e.id, e.name from Employee e", Tuple.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 1, as no 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.createQuery( + "select e.id, e.name from Employee e", Tuple.class ) + .getResultList(); + + assertEquals( 5, employees.size() ); + + //The miss count is still 1, as no we got the query plan from the cache + assertEquals( 1, statistics.getQueryPlanCacheMissCount() ); + //And the cache hit count increases. + assertEquals( 2, statistics.getQueryPlanCacheHitCount() ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13077") + public void testLockModeHitCount() { + statistics.clear(); + + doInJPA( this::entityManagerFactory, entityManager -> { + TypedQuery typedQuery = entityManager.createQuery( "select e from Employee e", Employee.class ); + + 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 + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + + typedQuery.setLockMode( LockModeType.READ ); + + //The hit count should still be 0 as setLockMode() shouldn't trigger a cache hit + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + + assertNotNull( typedQuery.getLockMode() ); + + //The hit count should still be 0 as getLockMode() shouldn't trigger a cache hit + assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); + } ); + } + private void assertQueryStatistics(String hql, int hitCount) { QueryStatistics queryStatistics = statistics.getQueryStatistics( hql ); @@ -125,6 +292,10 @@ private void assertQueryStatistics(String hql, int hitCount) { } @Entity(name = "Employee") + @NamedQuery( + name = "find_employee_by_name", + query = "select e from Employee e where e.name = :name" + ) public static class Employee { @Id