diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java index 11e918bdac..c330d4aed6 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryBinder.java @@ -67,6 +67,7 @@ public static void bindQuery( .setReadOnly( hints.getBoolean( queryName, QueryHints.READ_ONLY ) ) .setComment( hints.getString( queryName, QueryHints.COMMENT ) ) .setParameterTypes( null ) + .setPassDistinctThrough( hints.getPassDistinctThrough( queryName ) ) .createNamedQueryDefinition(); if ( isDefault ) { @@ -108,8 +109,9 @@ public static void bindNativeQuery( .setReadOnly( hints.getBoolean( queryName, QueryHints.READ_ONLY ) ) .setComment( hints.getString( queryName, QueryHints.COMMENT ) ) .setParameterTypes( null ) - .setCallable( hints.getBoolean( queryName, QueryHints.CALLABLE ) ); - + .setCallable( hints.getBoolean( queryName, QueryHints.CALLABLE ) ) + .setPassDistinctThrough( hints.getPassDistinctThrough( queryName ) ); + if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) { //sql result set usage builder.setResultSetRef( resultSetMapping ) diff --git a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryHintDefinition.java b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryHintDefinition.java index 0c7d9e9bf5..f69bacf01e 100644 --- a/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryHintDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/cfg/annotations/QueryHintDefinition.java @@ -9,6 +9,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import javax.persistence.LockModeType; import javax.persistence.NamedQuery; import javax.persistence.QueryHint; @@ -84,21 +85,28 @@ public LockMode getLockMode(String query) { } } + public Boolean getPassDistinctThrough(String query) { + return doGetBoolean( query, QueryHints.PASS_DISTINCT_THROUGH ).orElse( null ); + } + public boolean getBoolean(String query, String hintName) { - String value =(String) hintsMap.get( hintName ); + return doGetBoolean( query, hintName ).orElse( false ); + } + + private Optional doGetBoolean(String query, String hintName) { + String value = (String) hintsMap.get( hintName ); if ( value == null ) { - return false; + return Optional.empty(); } if ( value.equalsIgnoreCase( "true" ) ) { - return true; + return Optional.of( true ); } else if ( value.equalsIgnoreCase( "false" ) ) { - return false; + return Optional.of( false ); } else { throw new AnnotationException( "Not a boolean in hint: " + query + ":" + hintName ); } - } public String getString(String query, String hintName) { diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedQueryDefinition.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedQueryDefinition.java index 346412ab09..e9bc7b30a5 100755 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedQueryDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedQueryDefinition.java @@ -33,6 +33,7 @@ public class NamedQueryDefinition implements Serializable { private final CacheMode cacheMode; private final boolean readOnly; private final String comment; + private final Boolean passDistinctThrough; // added for jpa 2.1 private final Integer firstResult; @@ -133,7 +134,8 @@ public NamedQueryDefinition( comment, parameterTypes, null, // firstResult - null // maxResults + null, // maxResults + null // passDistinctThrough ); } @@ -151,7 +153,8 @@ public NamedQueryDefinition( String comment, Map parameterTypes, Integer firstResult, - Integer maxResults) { + Integer maxResults, + Boolean passDistinctThrough) { this.name = name; this.query = query; this.cacheable = cacheable; @@ -167,6 +170,7 @@ public NamedQueryDefinition( this.firstResult = firstResult; this.maxResults = maxResults; + this.passDistinctThrough = passDistinctThrough; } public String getName() { @@ -230,6 +234,10 @@ public Integer getMaxResults() { return maxResults; } + public Boolean getPassDistinctThrough() { + return passDistinctThrough; + } + @Override public String toString() { return getClass().getName() + '(' + name + " [" + query + "])"; @@ -250,7 +258,8 @@ public NamedQueryDefinition makeCopy(String name) { getComment(), getParameterTypes(), getFirstResult(), - getMaxResults() + getMaxResults(), + getPassDistinctThrough() ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedQueryDefinitionBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedQueryDefinitionBuilder.java index 80e540f512..57562bcedf 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedQueryDefinitionBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedQueryDefinitionBuilder.java @@ -28,6 +28,7 @@ public class NamedQueryDefinitionBuilder { protected LockOptions lockOptions; protected Integer firstResult; protected Integer maxResults; + protected Boolean passDistinctThrough; public NamedQueryDefinitionBuilder() { } @@ -114,6 +115,11 @@ public NamedQueryDefinitionBuilder setMaxResults(Integer maxResults) { return this; } + public NamedQueryDefinitionBuilder setPassDistinctThrough(Boolean passDistinctThrough) { + this.passDistinctThrough = passDistinctThrough; + return this; + } + public NamedQueryDefinition createNamedQueryDefinition() { return new NamedQueryDefinition( name, @@ -129,7 +135,8 @@ public NamedQueryDefinition createNamedQueryDefinition() { comment, parameterTypes, firstResult, - maxResults + maxResults, + passDistinctThrough ); } } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java index 782fb9e47e..fd58c47fa8 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinition.java @@ -81,7 +81,8 @@ public NamedSQLQueryDefinition( null, // resultSetRef querySpaces, callable, - queryReturns + queryReturns, + null // passDistinctThrough ); } @@ -140,7 +141,8 @@ public NamedSQLQueryDefinition( resultSetRef, querySpaces, callable, - null // queryReturns + null, // queryReturns + null // passDistinctThrough ); } @@ -161,7 +163,8 @@ public NamedSQLQueryDefinition( String resultSetRef, List querySpaces, boolean callable, - NativeSQLQueryReturn[] queryReturns) { + NativeSQLQueryReturn[] queryReturns, + Boolean passDistinctThrough) { super( name, query.trim(), /* trim done to workaround stupid oracle bug that cant handle whitespaces before a { in a sp */ @@ -176,7 +179,8 @@ public NamedSQLQueryDefinition( comment, parameterTypes, firstResult, - maxResults + maxResults, + passDistinctThrough ); this.resultSetRef = resultSetRef; this.querySpaces = querySpaces; @@ -219,7 +223,8 @@ public NamedSQLQueryDefinition makeCopy(String name) { getResultSetRef(), getQuerySpaces(), isCallable(), - getQueryReturns() + getQueryReturns(), + getPassDistinctThrough() ); } diff --git a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinitionBuilder.java b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinitionBuilder.java index 939295bc74..6824263023 100644 --- a/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinitionBuilder.java +++ b/hibernate-core/src/main/java/org/hibernate/engine/spi/NamedSQLQueryDefinitionBuilder.java @@ -149,6 +149,11 @@ public NamedSQLQueryDefinitionBuilder setMaxResults(Integer maxResults) { return (NamedSQLQueryDefinitionBuilder) super.setMaxResults( maxResults ); } + @Override + public NamedSQLQueryDefinitionBuilder setPassDistinctThrough(Boolean passDistinctThrough) { + return (NamedSQLQueryDefinitionBuilder) super.setPassDistinctThrough( passDistinctThrough ); + } + @Override public NamedSQLQueryDefinition createNamedQueryDefinition() { return new NamedSQLQueryDefinition( @@ -168,7 +173,8 @@ public NamedSQLQueryDefinition createNamedQueryDefinition() { resultSetRef, querySpacesCopy(), callable, - queryReturns + queryReturns, + passDistinctThrough ); } 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 ea3bc13aec..f9f6317ad7 100644 --- a/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java +++ b/hibernate-core/src/main/java/org/hibernate/internal/AbstractSharedSessionContract.java @@ -62,6 +62,7 @@ import org.hibernate.engine.transaction.internal.TransactionImpl; import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.id.uuid.StandardRandomStrategy; +import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.jpa.spi.CriteriaQueryTupleTransformer; import org.hibernate.jpa.spi.HibernateEntityManagerImplementor; @@ -695,6 +696,9 @@ protected void initQueryFromNamedDefinition(Query query, NamedQueryDefinition nq if ( nqd.getFlushMode() != null ) { query.setHibernateFlushMode( nqd.getFlushMode() ); } + if ( nqd.getPassDistinctThrough() != null ) { + query.setHint( QueryHints.HINT_PASS_DISTINCT_THROUGH, nqd.getPassDistinctThrough() ); + } } @Override diff --git a/hibernate-core/src/test/java/org/hibernate/test/distinct/SelectDistinctHqlTest.java b/hibernate-core/src/test/java/org/hibernate/test/distinct/SelectDistinctHqlTest.java index df13035c24..77ff5c5d17 100644 --- a/hibernate-core/src/test/java/org/hibernate/test/distinct/SelectDistinctHqlTest.java +++ b/hibernate-core/src/test/java/org/hibernate/test/distinct/SelectDistinctHqlTest.java @@ -14,6 +14,9 @@ import javax.persistence.Id; import javax.persistence.ManyToOne; import javax.persistence.OneToMany; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.QueryHint; import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.jpa.QueryHints; @@ -34,6 +37,9 @@ @TestForIssue( jiraKey = "HHH-10965" ) public class SelectDistinctHqlTest extends BaseNonConfigCoreFunctionalTestCase { + private static final String DISTINCT_PASSES_THROUGH_TRUE_NAMED_QUERY = "distinctPassesThroughTrue"; + private static final String DISTINCT_PASSES_THROUGH_FALSE_NAMED_QUERY = "distinctPassesThroughFalse"; + private static final String DISTINCT_PASSES_THROUGH_NOT_SPECIFIED_NAMED_QUERY = "distinctPassesThroughNotSpecified"; private SQLStatementInterceptor sqlStatementInterceptor; @Override @@ -137,7 +143,65 @@ public void testDistinctPassThroughTrue() { }); } + @Test + @TestForIssue(jiraKey = "HHH-13780") + public void testNamedQueryDistinctPassThroughTrue() { + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.getSqlQueries().clear(); + List persons = session.createNamedQuery( DISTINCT_PASSES_THROUGH_TRUE_NAMED_QUERY, Person.class ) + .setMaxResults( 5 ) + .getResultList(); + assertEquals( 1, persons.size() ); + String sqlQuery = sqlStatementInterceptor.getSqlQueries().getLast(); + assertTrue( sqlQuery.contains( " distinct " ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13780") + public void testNamedQueryDistinctPassThroughTrueWhenNotSpecified() { + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.getSqlQueries().clear(); + List persons = + session.createNamedQuery( DISTINCT_PASSES_THROUGH_NOT_SPECIFIED_NAMED_QUERY, Person.class ) + .setMaxResults( 5 ) + .getResultList(); + assertEquals( 1, persons.size() ); + String sqlQuery = sqlStatementInterceptor.getSqlQueries().getLast(); + assertTrue( sqlQuery.contains( " distinct " ) ); + } ); + } + + @Test + @TestForIssue(jiraKey = "HHH-13780") + public void testNamedQueryDistinctPassThroughFalse() { + doInHibernate( this::sessionFactory, session -> { + sqlStatementInterceptor.getSqlQueries().clear(); + List persons = + session.createNamedQuery( DISTINCT_PASSES_THROUGH_FALSE_NAMED_QUERY, Person.class ) + .setMaxResults( 5 ) + .getResultList(); + assertEquals( 1, persons.size() ); + String sqlQuery = sqlStatementInterceptor.getSqlQueries().getLast(); + assertFalse( sqlQuery.contains( " distinct " ) ); + } ); + } + @Entity(name = "Person") + @NamedQueries({ + @NamedQuery(name = DISTINCT_PASSES_THROUGH_TRUE_NAMED_QUERY, + query = "select distinct p from Person p left join fetch p.phones", + hints = { + @QueryHint(name = QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "true") + }), + @NamedQuery(name = DISTINCT_PASSES_THROUGH_FALSE_NAMED_QUERY, + query = "select distinct p from Person p left join fetch p.phones", + hints = { + @QueryHint(name = QueryHints.HINT_PASS_DISTINCT_THROUGH, value = "false") + }), + @NamedQuery(name = DISTINCT_PASSES_THROUGH_NOT_SPECIFIED_NAMED_QUERY, + query = "select distinct p from Person p left join fetch p.phones") + }) public static class Person { @Id