HHH-13077 - Optimize query plan call count
This commit is contained in:
parent
35bd8b4b6f
commit
7ef27cbb26
|
@ -638,11 +638,11 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
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 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
}
|
||||
|
||||
@Override
|
||||
public QueryImplementor createQuery(String queryString) {
|
||||
public QueryImpl createQuery(String queryString) {
|
||||
checkOpen();
|
||||
pulseTransactionCoordinator();
|
||||
delayedAfterCompletion();
|
||||
|
@ -722,7 +722,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
try {
|
||||
final QueryImpl query = new QueryImpl(
|
||||
this,
|
||||
getQueryPlan( queryString, false ).getParameterMetadata(),
|
||||
getQueryPlan( queryString, false ),
|
||||
queryString
|
||||
);
|
||||
applyQuerySettingsAndHints( query );
|
||||
|
@ -822,7 +822,7 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
|
||||
try {
|
||||
// do the translation
|
||||
final QueryImplementor<T> query = createQuery( queryString );
|
||||
final QueryImpl<T> query = createQuery( queryString );
|
||||
resultClassChecking( resultClass, query );
|
||||
return query;
|
||||
}
|
||||
|
@ -832,13 +832,10 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
}
|
||||
|
||||
@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 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
|
|||
|
||||
@SuppressWarnings({"WeakerAccess", "unchecked"})
|
||||
protected <T> QueryImplementor<T> createQuery(NamedQueryDefinition namedQueryDefinition, Class<T> resultType) {
|
||||
final QueryImplementor query = createQuery( namedQueryDefinition );
|
||||
final QueryImpl query = createQuery( namedQueryDefinition );
|
||||
if ( resultType != null ) {
|
||||
resultClassChecking( resultType, query );
|
||||
}
|
||||
|
|
|
@ -1523,7 +1523,7 @@ public class SessionImpl
|
|||
queryParameters.validateParameters();
|
||||
|
||||
HQLQueryPlan plan = queryParameters.getQueryPlan();
|
||||
if ( plan == null ) {
|
||||
if ( plan == null || !plan.isShallow() ) {
|
||||
plan = getQueryPlan( query, true );
|
||||
}
|
||||
|
||||
|
|
|
@ -67,7 +67,7 @@ import org.hibernate.type.Type;
|
|||
* @author Steve Ebersole
|
||||
* @author Gavin King
|
||||
*/
|
||||
@SuppressWarnings("UnusedDeclaration")
|
||||
@SuppressWarnings("UnusedDeclaratiqon")
|
||||
public interface Query<R> extends TypedQuery<R>, org.hibernate.Query<R>, CommonQueryContract {
|
||||
/**
|
||||
* Get the QueryProducer this Query originates from.
|
||||
|
|
|
@ -1434,8 +1434,8 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
);
|
||||
}
|
||||
|
||||
QueryParameters queryParameters = new QueryParameters(
|
||||
getQueryParameterBindings(),
|
||||
QueryParameters queryParameters = new QueryParameters(
|
||||
getQueryParameterBindings(),
|
||||
getLockOptions(),
|
||||
queryOptions,
|
||||
true,
|
||||
|
@ -1450,13 +1450,24 @@ public abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
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 abstract class AbstractProducedQuery<R> implements QueryImplementor<R> {
|
|||
: defaultType;
|
||||
}
|
||||
|
||||
private boolean isSelect() {
|
||||
protected boolean isSelect() {
|
||||
return getProducer().getFactory().getQueryPlanCache()
|
||||
.getHQLQueryPlan( getQueryString(), false, Collections.<String, Filter>emptyMap() )
|
||||
.isSelect();
|
||||
|
|
|
@ -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 @@ import org.hibernate.type.Type;
|
|||
public class QueryImpl<R> extends AbstractProducedQuery<R> implements Query<R> {
|
||||
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 class QueryImpl<R> extends AbstractProducedQuery<R> implements Query<R> {
|
|||
return queryString;
|
||||
}
|
||||
|
||||
public HQLQueryPlan getQueryPlan() {
|
||||
return hqlQueryPlan;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isNativeQuery() {
|
||||
return false;
|
||||
|
@ -50,12 +59,14 @@ public class QueryImpl<R> extends AbstractProducedQuery<R> implements Query<R> {
|
|||
|
||||
@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 class QueryImpl<R> extends AbstractProducedQuery<R> implements Query<R> {
|
|||
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() );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTes
|
|||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addConfigOptions(Map options) {
|
||||
options.put( Environment.GENERATE_STATISTICS, "true" );
|
||||
}
|
||||
|
@ -63,6 +70,7 @@ public class QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTes
|
|||
@Test
|
||||
public void test() {
|
||||
|
||||
statistics.clear();
|
||||
assertEquals( 0, statistics.getQueryPlanCacheHitCount() );
|
||||
assertEquals( 0, statistics.getQueryPlanCacheMissCount() );
|
||||
|
||||
|
@ -115,6 +123,165 @@ public class QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTes
|
|||
} );
|
||||
}
|
||||
|
||||
@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) {
|
||||
QueryStatistics queryStatistics = statistics.getQueryStatistics( hql );
|
||||
|
||||
|
@ -125,6 +292,10 @@ public class QueryPlanCacheStatisticsTest extends BaseEntityManagerFunctionalTes
|
|||
}
|
||||
|
||||
@Entity(name = "Employee")
|
||||
@NamedQuery(
|
||||
name = "find_employee_by_name",
|
||||
query = "select e from Employee e where e.name = :name"
|
||||
)
|
||||
public static class Employee {
|
||||
|
||||
@Id
|
||||
|
|
Loading…
Reference in New Issue