HHH-2736 query hints for Query and Criteria
This commit is contained in:
parent
6beb5acb4b
commit
742b1b4156
|
@ -25,6 +25,8 @@
|
|||
package org.hibernate;
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.QueryHint;
|
||||
|
||||
import org.hibernate.criterion.CriteriaSpecification;
|
||||
import org.hibernate.criterion.Criterion;
|
||||
import org.hibernate.criterion.Order;
|
||||
|
@ -506,6 +508,18 @@ public interface Criteria extends CriteriaSpecification {
|
|||
*/
|
||||
public Criteria setComment(String comment);
|
||||
|
||||
|
||||
/**
|
||||
* Add a DB query hint to the SQL. These differ from JPA's {@link QueryHint}, which is specific to the JPA
|
||||
* implementation and ignores DB vendor-specific hints. Instead, these are intended solely for the vendor-specific
|
||||
* hints, such as Oracle's optimizers. Multiple query hints are supported; the Dialect will determine
|
||||
* concatenation and placement.
|
||||
*
|
||||
* @param hint The database specific query hint to add.
|
||||
* @return this (for method chaining)
|
||||
*/
|
||||
public Criteria addQueryHint(String hint);
|
||||
|
||||
/**
|
||||
* Override the flush mode for this particular query.
|
||||
*
|
||||
|
|
|
@ -34,6 +34,8 @@ import java.util.List;
|
|||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.persistence.QueryHint;
|
||||
|
||||
import org.hibernate.transform.ResultTransformer;
|
||||
import org.hibernate.type.Type;
|
||||
|
||||
|
@ -212,6 +214,16 @@ public interface Query extends BasicQueryContract {
|
|||
*/
|
||||
public Query setComment(String comment);
|
||||
|
||||
/**
|
||||
* Add a DB query hint to the SQL. These differ from JPA's {@link QueryHint}, which is specific to the JPA
|
||||
* implementation and ignores DB vendor-specific hints. Instead, these are intended solely for the vendor-specific
|
||||
* hints, such as Oracle's optimizers. Multiple query hints are supported; the Dialect will determine
|
||||
* concatenation and placement.
|
||||
*
|
||||
* @param hint The database specific query hint to add.
|
||||
*/
|
||||
public Query addQueryHint(String hint);
|
||||
|
||||
/**
|
||||
* Return the HQL select clause aliases, if any.
|
||||
*
|
||||
|
|
|
@ -35,6 +35,7 @@ import java.sql.Types;
|
|||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Set;
|
||||
|
@ -2658,4 +2659,16 @@ public abstract class Dialect implements ConversionContext {
|
|||
public boolean supportsNotNullUnique() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a hint to the query. The entire query is provided, allowing the Dialect full control over the placement
|
||||
* and syntax of the hint. By default, ignore the hint and simply return the query.
|
||||
*
|
||||
* @param query The query to which to apply the hint.
|
||||
* @param hints The hints to apply
|
||||
* @return The modified SQL
|
||||
*/
|
||||
public String getQueryHintString(String query, List<String> hints) {
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,9 +27,11 @@ import java.sql.CallableStatement;
|
|||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Types;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.JDBCException;
|
||||
import org.hibernate.QueryTimeoutException;
|
||||
import org.hibernate.annotations.common.util.StringHelper;
|
||||
import org.hibernate.cfg.Environment;
|
||||
import org.hibernate.dialect.function.NoArgSQLFunction;
|
||||
import org.hibernate.dialect.function.NvlFunction;
|
||||
|
@ -582,4 +584,26 @@ public class Oracle8iDialect extends Dialect {
|
|||
public String getNotExpression( String expression ) {
|
||||
return "not (" + expression + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getQueryHintString(String sql, List<String> hints) {
|
||||
final String hint = StringHelper.join( ", ", hints.iterator() );
|
||||
|
||||
if ( StringHelper.isEmpty( hint ) ) {
|
||||
return sql;
|
||||
}
|
||||
|
||||
final int pos = sql.indexOf( "select" );
|
||||
if ( pos > -1 ) {
|
||||
final StringBuilder buffer = new StringBuilder( sql.length() + hint.length() + 8 );
|
||||
if ( pos > 0 ) {
|
||||
buffer.append( sql.substring( 0, pos ) );
|
||||
}
|
||||
buffer.append( "select /*+ " ).append( hint ).append( " */" )
|
||||
.append( sql.substring( pos + "select".length() ) );
|
||||
sql = buffer.toString();
|
||||
}
|
||||
|
||||
return sql;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@ public final class QueryParameters {
|
|||
private boolean cacheable;
|
||||
private String cacheRegion;
|
||||
private String comment;
|
||||
private List<String> queryHints;
|
||||
private ScrollMode scrollMode;
|
||||
private Serializable[] collectionKeys;
|
||||
private Object optionalObject;
|
||||
|
@ -101,7 +102,7 @@ public final class QueryParameters {
|
|||
public QueryParameters(
|
||||
final Type[] positionalParameterTypes,
|
||||
final Object[] positionalParameterValues) {
|
||||
this( positionalParameterTypes, positionalParameterValues, null, null, false, false, false, null, null, false, null );
|
||||
this( positionalParameterTypes, positionalParameterValues, null, null, false, false, false, null, null, null, false, null );
|
||||
}
|
||||
|
||||
public QueryParameters(
|
||||
|
@ -127,6 +128,7 @@ public final class QueryParameters {
|
|||
false,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
collectionKeys,
|
||||
null
|
||||
);
|
||||
|
@ -143,6 +145,7 @@ public final class QueryParameters {
|
|||
final String cacheRegion,
|
||||
//final boolean forceCacheRefresh,
|
||||
final String comment,
|
||||
final List<String> queryHints,
|
||||
final boolean isLookupByNaturalKey,
|
||||
final ResultTransformer transformer) {
|
||||
this(
|
||||
|
@ -156,6 +159,7 @@ public final class QueryParameters {
|
|||
cacheable,
|
||||
cacheRegion,
|
||||
comment,
|
||||
queryHints,
|
||||
null,
|
||||
transformer
|
||||
);
|
||||
|
@ -174,6 +178,7 @@ public final class QueryParameters {
|
|||
final String cacheRegion,
|
||||
//final boolean forceCacheRefresh,
|
||||
final String comment,
|
||||
final List<String> queryHints,
|
||||
final Serializable[] collectionKeys,
|
||||
ResultTransformer transformer) {
|
||||
this.positionalParameterTypes = positionalParameterTypes;
|
||||
|
@ -185,6 +190,7 @@ public final class QueryParameters {
|
|||
this.cacheRegion = cacheRegion;
|
||||
//this.forceCacheRefresh = forceCacheRefresh;
|
||||
this.comment = comment;
|
||||
this.queryHints = queryHints;
|
||||
this.collectionKeys = collectionKeys;
|
||||
this.isReadOnlyInitialized = isReadOnlyInitialized;
|
||||
this.readOnly = readOnly;
|
||||
|
@ -203,6 +209,7 @@ public final class QueryParameters {
|
|||
final String cacheRegion,
|
||||
//final boolean forceCacheRefresh,
|
||||
final String comment,
|
||||
final List<String> queryHints,
|
||||
final Serializable[] collectionKeys,
|
||||
final Object optionalObject,
|
||||
final String optionalEntityName,
|
||||
|
@ -219,6 +226,7 @@ public final class QueryParameters {
|
|||
cacheable,
|
||||
cacheRegion,
|
||||
comment,
|
||||
queryHints,
|
||||
collectionKeys,
|
||||
transformer
|
||||
);
|
||||
|
@ -323,6 +331,14 @@ public final class QueryParameters {
|
|||
this.comment = comment;
|
||||
}
|
||||
|
||||
public List<String> getQueryHints() {
|
||||
return queryHints;
|
||||
}
|
||||
|
||||
public void setQueryHints(List<String> queryHints) {
|
||||
this.queryHints = queryHints;
|
||||
}
|
||||
|
||||
public ScrollMode getScrollMode() {
|
||||
return scrollMode;
|
||||
}
|
||||
|
@ -561,6 +577,7 @@ public final class QueryParameters {
|
|||
this.cacheable,
|
||||
this.cacheRegion,
|
||||
this.comment,
|
||||
this.queryHints,
|
||||
this.collectionKeys,
|
||||
this.optionalObject,
|
||||
this.optionalEntityName,
|
||||
|
|
|
@ -101,6 +101,7 @@ public abstract class AbstractQueryImpl implements Query {
|
|||
private boolean cacheable;
|
||||
private String cacheRegion;
|
||||
private String comment;
|
||||
private final List<String> queryHints = new ArrayList<String>();
|
||||
private FlushMode flushMode;
|
||||
private CacheMode cacheMode;
|
||||
private FlushMode sessionFlushMode;
|
||||
|
@ -193,6 +194,12 @@ public abstract class AbstractQueryImpl implements Query {
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Query addQueryHint(String queryHint) {
|
||||
queryHints.add( queryHint );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer getFirstResult() {
|
||||
return selection.getFirstRow();
|
||||
|
@ -987,6 +994,7 @@ public abstract class AbstractQueryImpl implements Query {
|
|||
cacheable,
|
||||
cacheRegion,
|
||||
comment,
|
||||
queryHints,
|
||||
collectionKey == null ? null : new Serializable[] { collectionKey },
|
||||
optionalObject,
|
||||
optionalEntityName,
|
||||
|
|
|
@ -75,6 +75,7 @@ public class CriteriaImpl implements Criteria, Serializable {
|
|||
private boolean cacheable;
|
||||
private String cacheRegion;
|
||||
private String comment;
|
||||
private final List<String> queryHints = new ArrayList<String>();
|
||||
|
||||
private FlushMode flushMode;
|
||||
private CacheMode cacheMode;
|
||||
|
@ -345,11 +346,23 @@ public class CriteriaImpl implements Criteria, Serializable {
|
|||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria setComment(String comment) {
|
||||
this.comment = comment;
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<String> getQueryHints() {
|
||||
return queryHints;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria addQueryHint(String queryHint) {
|
||||
queryHints.add( queryHint );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Criteria setFlushMode(FlushMode flushMode) {
|
||||
this.flushMode = flushMode;
|
||||
|
@ -668,6 +681,11 @@ public class CriteriaImpl implements Criteria, Serializable {
|
|||
return this;
|
||||
}
|
||||
@Override
|
||||
public Criteria addQueryHint(String queryHint) {
|
||||
CriteriaImpl.this.addQueryHint( queryHint );
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
public Criteria setProjection(Projection projection) {
|
||||
CriteriaImpl.this.projection = projection;
|
||||
CriteriaImpl.this.projectionCriteria = this;
|
||||
|
|
|
@ -242,6 +242,13 @@ public abstract class Loader {
|
|||
Dialect dialect,
|
||||
List<AfterLoadAction> afterLoadActions) throws HibernateException {
|
||||
sql = applyLocks( sql, parameters, dialect, afterLoadActions );
|
||||
|
||||
// Keep this here, rather than moving to Select. Some Dialects may need the hint to be appended to the very
|
||||
// end or beginning of the finalized SQL statement, so wait until everything is processed.
|
||||
if ( parameters.getQueryHints() != null && parameters.getQueryHints().size() > 0 ) {
|
||||
sql = dialect.getQueryHintString( sql, parameters.getQueryHints() );
|
||||
}
|
||||
|
||||
return getFactory().getSettings().isCommentsEnabled()
|
||||
? prependComment( sql, parameters )
|
||||
: sql;
|
||||
|
@ -1844,7 +1851,7 @@ public abstract class Loader {
|
|||
* limit parameters.
|
||||
*/
|
||||
protected final PreparedStatement prepareQueryStatement(
|
||||
final String sql,
|
||||
String sql,
|
||||
final QueryParameters queryParameters,
|
||||
final LimitHandler limitHandler,
|
||||
final boolean scroll,
|
||||
|
|
|
@ -355,6 +355,7 @@ public class CriteriaQueryTranslator implements CriteriaQuery {
|
|||
rootCriteria.getCacheable(),
|
||||
rootCriteria.getCacheRegion(),
|
||||
rootCriteria.getComment(),
|
||||
rootCriteria.getQueryHints(),
|
||||
rootCriteria.isLookupByNaturalKey(),
|
||||
rootCriteria.getResultTransformer()
|
||||
);
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* JBoss, Home of Professional Open Source
|
||||
* Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @authors tag. All rights reserved.
|
||||
* See the copyright.txt in the distribution for a
|
||||
* full listing of individual contributors.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use,
|
||||
* modify, copy, or redistribute it subject to the terms and conditions
|
||||
* of the GNU Lesser General Public License, v. 2.1.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT A
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
* You should have received a copy of the GNU Lesser General Public License,
|
||||
* v.2.1 along with this distribution; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
package org.hibernate.test.queryhint;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import javax.persistence.Entity;
|
||||
import javax.persistence.GeneratedValue;
|
||||
import javax.persistence.Id;
|
||||
import javax.persistence.ManyToOne;
|
||||
|
||||
import org.hibernate.Criteria;
|
||||
import org.hibernate.Query;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.cfg.AvailableSettings;
|
||||
import org.hibernate.cfg.Configuration;
|
||||
import org.hibernate.criterion.Restrictions;
|
||||
import org.hibernate.dialect.Oracle8iDialect;
|
||||
import org.hibernate.testing.RequiresDialect;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* @author Brett Meyer
|
||||
*/
|
||||
@RequiresDialect( Oracle8iDialect.class )
|
||||
public class QueryHintTest extends BaseCoreFunctionalTestCase {
|
||||
|
||||
@Override
|
||||
protected Class<?>[] getAnnotatedClasses() {
|
||||
return new Class<?>[] { Employee.class, Department.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(Configuration configuration) {
|
||||
configuration.setProperty( AvailableSettings.DIALECT, QueryHintTestDialect.class.getName() );
|
||||
configuration.setProperty( AvailableSettings.USE_SQL_COMMENTS, "true" );
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testQueryHint() {
|
||||
Department department = new Department();
|
||||
department.name = "Sales";
|
||||
Employee employee1 = new Employee();
|
||||
employee1.department = department;
|
||||
Employee employee2 = new Employee();
|
||||
employee2.department = department;
|
||||
|
||||
Session s = openSession();
|
||||
s.getTransaction().begin();
|
||||
s.persist( department ); s.persist( employee1 );
|
||||
s.persist( employee2 );
|
||||
s.getTransaction().commit();
|
||||
s.clear();
|
||||
|
||||
// test Query w/ a simple Oracle optimizer hint
|
||||
s.getTransaction().begin();
|
||||
Query query = s.createQuery( "FROM QueryHintTest$Employee e WHERE e.department.name = :departmentName" )
|
||||
.addQueryHint( "ALL_ROWS" )
|
||||
.setParameter( "departmentName", "Sales" );
|
||||
List results = query.list();
|
||||
s.getTransaction().commit();
|
||||
s.clear();
|
||||
|
||||
assertEquals(results.size(), 2);
|
||||
assertTrue(QueryHintTestDialect.getProcessedSql().contains( "select /*+ ALL_ROWS */"));
|
||||
|
||||
QueryHintTestDialect.resetProcessedSql();
|
||||
|
||||
// test multiple hints
|
||||
s.getTransaction().begin();
|
||||
query = s.createQuery( "FROM QueryHintTest$Employee e WHERE e.department.name = :departmentName" )
|
||||
.addQueryHint( "ALL_ROWS" )
|
||||
.addQueryHint( "USE_CONCAT" )
|
||||
.setParameter( "departmentName", "Sales" );
|
||||
results = query.list();
|
||||
s.getTransaction().commit();
|
||||
s.clear();
|
||||
|
||||
assertEquals(results.size(), 2);
|
||||
assertTrue(QueryHintTestDialect.getProcessedSql().contains( "select /*+ ALL_ROWS, USE_CONCAT */"));
|
||||
|
||||
QueryHintTestDialect.resetProcessedSql();
|
||||
|
||||
// ensure the insertion logic can handle a comment appended to the front
|
||||
s.getTransaction().begin();
|
||||
query = s.createQuery( "FROM QueryHintTest$Employee e WHERE e.department.name = :departmentName" )
|
||||
.setComment( "this is a test" )
|
||||
.addQueryHint( "ALL_ROWS" )
|
||||
.setParameter( "departmentName", "Sales" );
|
||||
results = query.list();
|
||||
s.getTransaction().commit();
|
||||
s.clear();
|
||||
|
||||
assertEquals(results.size(), 2);
|
||||
assertTrue(QueryHintTestDialect.getProcessedSql().contains( "select /*+ ALL_ROWS */"));
|
||||
|
||||
QueryHintTestDialect.resetProcessedSql();
|
||||
|
||||
// test Criteria
|
||||
s.getTransaction().begin();
|
||||
Criteria criteria = s.createCriteria( Employee.class )
|
||||
.addQueryHint( "ALL_ROWS" )
|
||||
.createCriteria( "department" ).add( Restrictions.eq( "name", "Sales" ) );
|
||||
results = criteria.list();
|
||||
s.getTransaction().commit();
|
||||
s.close();
|
||||
|
||||
assertEquals(results.size(), 2);
|
||||
assertTrue(QueryHintTestDialect.getProcessedSql().contains( "select /*+ ALL_ROWS */"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Since the query hint is added to the SQL during Loader's executeQueryStatement -> preprocessSQL, rather than
|
||||
* early on during the QueryTranslator or QueryLoader initialization, there's not an easy way to check the full SQL
|
||||
* after completely processing it. Instead, use this ridiculous hack to ensure Loader actually calls Dialect.
|
||||
*
|
||||
* TODO: This is terrible. Better ideas?
|
||||
*/
|
||||
public static class QueryHintTestDialect extends Oracle8iDialect {
|
||||
private static String processedSql;
|
||||
|
||||
@Override
|
||||
public String getQueryHintString(String sql, List<String> hints) {
|
||||
processedSql = super.getQueryHintString( sql, hints );
|
||||
return processedSql;
|
||||
}
|
||||
|
||||
public static String getProcessedSql() {
|
||||
return processedSql;
|
||||
}
|
||||
|
||||
public static void resetProcessedSql() {
|
||||
processedSql = "";
|
||||
}
|
||||
}
|
||||
|
||||
@Entity
|
||||
public static class Employee {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
public long id;
|
||||
|
||||
@ManyToOne
|
||||
public Department department;
|
||||
}
|
||||
|
||||
@Entity
|
||||
public static class Department {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
public long id;
|
||||
|
||||
public String name;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue