HHH-13077 - Optimize query plan call count

This commit is contained in:
Vlad Mihalcea 2018-11-12 13:35:21 +02:00 committed by Sanne Grinovero
parent 35bd8b4b6f
commit 7ef27cbb26
6 changed files with 233 additions and 25 deletions

View File

@ -638,11 +638,11 @@ public QueryImplementor getNamedQuery(String name) {
throw getExceptionConverter().convert( new IllegalArgumentException( "No query defined for that name [" + 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(); String queryString = queryDefinition.getQueryString();
final QueryImpl query = new QueryImpl( final QueryImpl query = new QueryImpl(
this, this,
getQueryPlan( queryString, false ).getParameterMetadata(), getQueryPlan( queryString, false ),
queryString queryString
); );
applyQuerySettingsAndHints( query ); applyQuerySettingsAndHints( query );
@ -714,7 +714,7 @@ protected void initQueryFromNamedDefinition(Query query, NamedQueryDefinition nq
} }
@Override @Override
public QueryImplementor createQuery(String queryString) { public QueryImpl createQuery(String queryString) {
checkOpen(); checkOpen();
pulseTransactionCoordinator(); pulseTransactionCoordinator();
delayedAfterCompletion(); delayedAfterCompletion();
@ -722,7 +722,7 @@ public QueryImplementor createQuery(String queryString) {
try { try {
final QueryImpl query = new QueryImpl( final QueryImpl query = new QueryImpl(
this, this,
getQueryPlan( queryString, false ).getParameterMetadata(), getQueryPlan( queryString, false ),
queryString queryString
); );
applyQuerySettingsAndHints( query ); applyQuerySettingsAndHints( query );
@ -822,7 +822,7 @@ public <T> QueryImplementor<T> createQuery(String queryString, Class<T> resultCl
try { try {
// do the translation // do the translation
final QueryImplementor<T> query = createQuery( queryString ); final QueryImpl<T> query = createQuery( queryString );
resultClassChecking( resultClass, query ); resultClassChecking( resultClass, query );
return query; return query;
} }
@ -832,13 +832,10 @@ public <T> QueryImplementor<T> createQuery(String queryString, Class<T> resultCl
} }
@SuppressWarnings({"unchecked", "WeakerAccess", "StatementWithEmptyBody"}) @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 // make sure the query is a select -> HHH-7192
final HQLQueryPlan queryPlan = getFactory().getQueryPlanCache().getHQLQueryPlan( HQLQueryPlan queryPlan = hqlQuery.getQueryPlan();
hqlQuery.getQueryString(),
false,
getLoadQueryInfluencers().getEnabledFilters()
);
if ( queryPlan.getTranslators()[0].isManipulationStatement() ) { if ( queryPlan.getTranslators()[0].isManipulationStatement() ) {
throw new IllegalArgumentException( "Update/delete queries cannot be typed" ); throw new IllegalArgumentException( "Update/delete queries cannot be typed" );
} }
@ -914,7 +911,7 @@ protected <T> QueryImplementor<T> buildQueryFromName(String name, Class<T> resu
@SuppressWarnings({"WeakerAccess", "unchecked"}) @SuppressWarnings({"WeakerAccess", "unchecked"})
protected <T> QueryImplementor<T> createQuery(NamedQueryDefinition namedQueryDefinition, Class<T> resultType) { protected <T> QueryImplementor<T> createQuery(NamedQueryDefinition namedQueryDefinition, Class<T> resultType) {
final QueryImplementor query = createQuery( namedQueryDefinition ); final QueryImpl query = createQuery( namedQueryDefinition );
if ( resultType != null ) { if ( resultType != null ) {
resultClassChecking( resultType, query ); resultClassChecking( resultType, query );
} }

View File

@ -1523,7 +1523,7 @@ public Iterator iterate(String query, QueryParameters queryParameters) throws Hi
queryParameters.validateParameters(); queryParameters.validateParameters();
HQLQueryPlan plan = queryParameters.getQueryPlan(); HQLQueryPlan plan = queryParameters.getQueryPlan();
if ( plan == null ) { if ( plan == null || !plan.isShallow() ) {
plan = getQueryPlan( query, true ); plan = getQueryPlan( query, true );
} }

View File

@ -67,7 +67,7 @@
* @author Steve Ebersole * @author Steve Ebersole
* @author Gavin King * @author Gavin King
*/ */
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaratiqon")
public interface Query<R> extends TypedQuery<R>, org.hibernate.Query<R>, CommonQueryContract { public interface Query<R> extends TypedQuery<R>, org.hibernate.Query<R>, CommonQueryContract {
/** /**
* Get the QueryProducer this Query originates from. * Get the QueryProducer this Query originates from.

View File

@ -1434,8 +1434,8 @@ protected QueryParameters makeQueryParametersForExecution(String hql) {
); );
} }
QueryParameters queryParameters = new QueryParameters( QueryParameters queryParameters = new QueryParameters(
getQueryParameterBindings(), getQueryParameterBindings(),
getLockOptions(), getLockOptions(),
queryOptions, queryOptions,
true, true,
@ -1450,13 +1450,24 @@ protected QueryParameters makeQueryParametersForExecution(String hql) {
optionalId, optionalId,
resultTransformer resultTransformer
); );
queryParameters.setQueryPlan( entityGraphHintedQueryPlan );
appendQueryPlanToQueryParameters( hql, queryParameters, entityGraphHintedQueryPlan );
if ( passDistinctThrough != null ) { if ( passDistinctThrough != null ) {
queryParameters.setPassDistinctThrough( passDistinctThrough ); queryParameters.setPassDistinctThrough( passDistinctThrough );
} }
return queryParameters; return queryParameters;
} }
protected void appendQueryPlanToQueryParameters(
String hql,
QueryParameters queryParameters,
HQLQueryPlan queryPlan) {
if ( queryPlan != null ) {
queryParameters.setQueryPlan( queryPlan );
}
}
public QueryParameters getQueryParameters() { public QueryParameters getQueryParameters() {
final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() ); final String expandedQuery = getQueryParameterBindings().expandListValuedParameters( getQueryString(), getProducer() );
return makeQueryParametersForExecution( expandedQuery ); return makeQueryParametersForExecution( expandedQuery );
@ -1732,7 +1743,7 @@ public Type determineProperBooleanType(int position, Object value, Type defaultT
: defaultType; : defaultType;
} }
private boolean isSelect() { protected boolean isSelect() {
return getProducer().getFactory().getQueryPlanCache() return getProducer().getFactory().getQueryPlanCache()
.getHQLQueryPlan( getQueryString(), false, Collections.<String, Filter>emptyMap() ) .getHQLQueryPlan( getQueryString(), false, Collections.<String, Filter>emptyMap() )
.isSelect(); .isSelect();

View File

@ -6,8 +6,10 @@
*/ */
package org.hibernate.query.internal; 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.engine.spi.SharedSessionContractImplementor;
import org.hibernate.query.ParameterMetadata;
import org.hibernate.query.Query; import org.hibernate.query.Query;
import org.hibernate.query.spi.QueryParameterBindings; import org.hibernate.query.spi.QueryParameterBindings;
import org.hibernate.type.Type; import org.hibernate.type.Type;
@ -18,16 +20,19 @@
public class QueryImpl<R> extends AbstractProducedQuery<R> implements Query<R> { public class QueryImpl<R> extends AbstractProducedQuery<R> implements Query<R> {
private final String queryString; private final String queryString;
private final HQLQueryPlan hqlQueryPlan;
private final QueryParameterBindingsImpl queryParameterBindings; private final QueryParameterBindingsImpl queryParameterBindings;
public QueryImpl( public QueryImpl(
SharedSessionContractImplementor producer, SharedSessionContractImplementor producer,
ParameterMetadata parameterMetadata, HQLQueryPlan hqlQueryPlan,
String queryString) { String queryString) {
super( producer, parameterMetadata ); super( producer, hqlQueryPlan.getParameterMetadata() );
this.hqlQueryPlan = hqlQueryPlan;
this.queryString = queryString; this.queryString = queryString;
this.queryParameterBindings = QueryParameterBindingsImpl.from( this.queryParameterBindings = QueryParameterBindingsImpl.from(
parameterMetadata, hqlQueryPlan.getParameterMetadata(),
producer.getFactory(), producer.getFactory(),
producer.isQueryParametersValidationEnabled() producer.isQueryParametersValidationEnabled()
); );
@ -43,6 +48,10 @@ public String getQueryString() {
return queryString; return queryString;
} }
public HQLQueryPlan getQueryPlan() {
return hqlQueryPlan;
}
@Override @Override
protected boolean isNativeQuery() { protected boolean isNativeQuery() {
return false; return false;
@ -50,12 +59,14 @@ protected boolean isNativeQuery() {
@Override @Override
public Type[] getReturnTypes() { public Type[] getReturnTypes() {
return getProducer().getFactory().getReturnTypes( queryString ); final ReturnMetadata metadata = hqlQueryPlan.getReturnMetadata();
return metadata == null ? null : metadata.getReturnTypes();
} }
@Override @Override
public String[] getReturnAliases() { public String[] getReturnAliases() {
return getProducer().getFactory().getReturnAliases( queryString ); final ReturnMetadata metadata = hqlQueryPlan.getReturnMetadata();
return metadata == null ? null : metadata.getReturnAliases();
} }
@Override @Override
@ -67,4 +78,22 @@ public Query setEntity(int position, Object val) {
public Query setEntity(String name, Object val) { public Query setEntity(String name, Object val) {
return setParameter( name, val, getProducer().getFactory().getTypeHelper().entity( resolveEntityName( 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() );
}
}
} }

View File

@ -6,22 +6,28 @@
*/ */
package org.hibernate.stat.internal; package org.hibernate.stat.internal;
import java.util.List;
import java.util.Map; import java.util.Map;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.GeneratedValue; import javax.persistence.GeneratedValue;
import javax.persistence.Id; 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.SessionFactory;
import org.hibernate.cfg.Environment; import org.hibernate.cfg.Environment;
import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase;
import org.hibernate.stat.QueryStatistics; import org.hibernate.stat.QueryStatistics;
import org.hibernate.stat.Statistics; import org.hibernate.stat.Statistics;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.junit.Test; import org.junit.Test;
import static org.hibernate.testing.transaction.TransactionUtil.doInJPA; import static org.hibernate.testing.transaction.TransactionUtil.doInJPA;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
/** /**
@ -39,6 +45,7 @@ public Class[] getAnnotatedClasses() {
}; };
} }
@Override
protected void addConfigOptions(Map options) { protected void addConfigOptions(Map options) {
options.put( Environment.GENERATE_STATISTICS, "true" ); options.put( Environment.GENERATE_STATISTICS, "true" );
} }
@ -63,6 +70,7 @@ protected void afterEntityManagerFactoryBuilt() {
@Test @Test
public void test() { public void test() {
statistics.clear();
assertEquals( 0, statistics.getQueryPlanCacheHitCount() ); assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
assertEquals( 0, statistics.getQueryPlanCacheMissCount() ); 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<Employee> 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<Employee> 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<Employee> 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<Tuple> 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<Tuple> 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<Tuple> 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<Employee> typedQuery = entityManager.createQuery( "select e from Employee e", Employee.class );
List<Employee> 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) { private void assertQueryStatistics(String hql, int hitCount) {
QueryStatistics queryStatistics = statistics.getQueryStatistics( hql ); QueryStatistics queryStatistics = statistics.getQueryStatistics( hql );
@ -125,6 +292,10 @@ private void assertQueryStatistics(String hql, int hitCount) {
} }
@Entity(name = "Employee") @Entity(name = "Employee")
@NamedQuery(
name = "find_employee_by_name",
query = "select e from Employee e where e.name = :name"
)
public static class Employee { public static class Employee {
@Id @Id