HHH-13780 Allow NamedQuery to set hint QueryHints.PASS_DISTINCT_THROUGH

This commit is contained in:
marekchodak 2020-01-19 22:04:47 +01:00 committed by Sanne Grinovero
parent 63a96e335e
commit bf473681e4
8 changed files with 122 additions and 17 deletions

View File

@ -67,6 +67,7 @@ public abstract class QueryBinder {
.setReadOnly( hints.getBoolean( queryName, QueryHints.READ_ONLY ) ) .setReadOnly( hints.getBoolean( queryName, QueryHints.READ_ONLY ) )
.setComment( hints.getString( queryName, QueryHints.COMMENT ) ) .setComment( hints.getString( queryName, QueryHints.COMMENT ) )
.setParameterTypes( null ) .setParameterTypes( null )
.setPassDistinctThrough( hints.getPassDistinctThrough( queryName ) )
.createNamedQueryDefinition(); .createNamedQueryDefinition();
if ( isDefault ) { if ( isDefault ) {
@ -108,8 +109,9 @@ public abstract class QueryBinder {
.setReadOnly( hints.getBoolean( queryName, QueryHints.READ_ONLY ) ) .setReadOnly( hints.getBoolean( queryName, QueryHints.READ_ONLY ) )
.setComment( hints.getString( queryName, QueryHints.COMMENT ) ) .setComment( hints.getString( queryName, QueryHints.COMMENT ) )
.setParameterTypes( null ) .setParameterTypes( null )
.setCallable( hints.getBoolean( queryName, QueryHints.CALLABLE ) ); .setCallable( hints.getBoolean( queryName, QueryHints.CALLABLE ) )
.setPassDistinctThrough( hints.getPassDistinctThrough( queryName ) );
if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) { if ( !BinderHelper.isEmptyAnnotationValue( resultSetMapping ) ) {
//sql result set usage //sql result set usage
builder.setResultSetRef( resultSetMapping ) builder.setResultSetRef( resultSetMapping )

View File

@ -9,6 +9,7 @@ package org.hibernate.cfg.annotations;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import javax.persistence.LockModeType; import javax.persistence.LockModeType;
import javax.persistence.NamedQuery; import javax.persistence.NamedQuery;
import javax.persistence.QueryHint; import javax.persistence.QueryHint;
@ -84,21 +85,28 @@ public class QueryHintDefinition {
} }
} }
public Boolean getPassDistinctThrough(String query) {
return doGetBoolean( query, QueryHints.PASS_DISTINCT_THROUGH ).orElse( null );
}
public boolean getBoolean(String query, String hintName) { public boolean getBoolean(String query, String hintName) {
String value =(String) hintsMap.get( hintName ); return doGetBoolean( query, hintName ).orElse( false );
}
private Optional<Boolean> doGetBoolean(String query, String hintName) {
String value = (String) hintsMap.get( hintName );
if ( value == null ) { if ( value == null ) {
return false; return Optional.empty();
} }
if ( value.equalsIgnoreCase( "true" ) ) { if ( value.equalsIgnoreCase( "true" ) ) {
return true; return Optional.of( true );
} }
else if ( value.equalsIgnoreCase( "false" ) ) { else if ( value.equalsIgnoreCase( "false" ) ) {
return false; return Optional.of( false );
} }
else { else {
throw new AnnotationException( "Not a boolean in hint: " + query + ":" + hintName ); throw new AnnotationException( "Not a boolean in hint: " + query + ":" + hintName );
} }
} }
public String getString(String query, String hintName) { public String getString(String query, String hintName) {

View File

@ -33,6 +33,7 @@ public class NamedQueryDefinition implements Serializable {
private final CacheMode cacheMode; private final CacheMode cacheMode;
private final boolean readOnly; private final boolean readOnly;
private final String comment; private final String comment;
private final Boolean passDistinctThrough;
// added for jpa 2.1 // added for jpa 2.1
private final Integer firstResult; private final Integer firstResult;
@ -133,7 +134,8 @@ public class NamedQueryDefinition implements Serializable {
comment, comment,
parameterTypes, parameterTypes,
null, // firstResult null, // firstResult
null // maxResults null, // maxResults
null // passDistinctThrough
); );
} }
@ -151,7 +153,8 @@ public class NamedQueryDefinition implements Serializable {
String comment, String comment,
Map parameterTypes, Map parameterTypes,
Integer firstResult, Integer firstResult,
Integer maxResults) { Integer maxResults,
Boolean passDistinctThrough) {
this.name = name; this.name = name;
this.query = query; this.query = query;
this.cacheable = cacheable; this.cacheable = cacheable;
@ -167,6 +170,7 @@ public class NamedQueryDefinition implements Serializable {
this.firstResult = firstResult; this.firstResult = firstResult;
this.maxResults = maxResults; this.maxResults = maxResults;
this.passDistinctThrough = passDistinctThrough;
} }
public String getName() { public String getName() {
@ -230,6 +234,10 @@ public class NamedQueryDefinition implements Serializable {
return maxResults; return maxResults;
} }
public Boolean getPassDistinctThrough() {
return passDistinctThrough;
}
@Override @Override
public String toString() { public String toString() {
return getClass().getName() + '(' + name + " [" + query + "])"; return getClass().getName() + '(' + name + " [" + query + "])";
@ -250,7 +258,8 @@ public class NamedQueryDefinition implements Serializable {
getComment(), getComment(),
getParameterTypes(), getParameterTypes(),
getFirstResult(), getFirstResult(),
getMaxResults() getMaxResults(),
getPassDistinctThrough()
); );
} }
} }

View File

@ -28,6 +28,7 @@ public class NamedQueryDefinitionBuilder {
protected LockOptions lockOptions; protected LockOptions lockOptions;
protected Integer firstResult; protected Integer firstResult;
protected Integer maxResults; protected Integer maxResults;
protected Boolean passDistinctThrough;
public NamedQueryDefinitionBuilder() { public NamedQueryDefinitionBuilder() {
} }
@ -114,6 +115,11 @@ public class NamedQueryDefinitionBuilder {
return this; return this;
} }
public NamedQueryDefinitionBuilder setPassDistinctThrough(Boolean passDistinctThrough) {
this.passDistinctThrough = passDistinctThrough;
return this;
}
public NamedQueryDefinition createNamedQueryDefinition() { public NamedQueryDefinition createNamedQueryDefinition() {
return new NamedQueryDefinition( return new NamedQueryDefinition(
name, name,
@ -129,7 +135,8 @@ public class NamedQueryDefinitionBuilder {
comment, comment,
parameterTypes, parameterTypes,
firstResult, firstResult,
maxResults maxResults,
passDistinctThrough
); );
} }
} }

View File

@ -81,7 +81,8 @@ public class NamedSQLQueryDefinition extends NamedQueryDefinition {
null, // resultSetRef null, // resultSetRef
querySpaces, querySpaces,
callable, callable,
queryReturns queryReturns,
null // passDistinctThrough
); );
} }
@ -140,7 +141,8 @@ public class NamedSQLQueryDefinition extends NamedQueryDefinition {
resultSetRef, resultSetRef,
querySpaces, querySpaces,
callable, callable,
null // queryReturns null, // queryReturns
null // passDistinctThrough
); );
} }
@ -161,7 +163,8 @@ public class NamedSQLQueryDefinition extends NamedQueryDefinition {
String resultSetRef, String resultSetRef,
List<String> querySpaces, List<String> querySpaces,
boolean callable, boolean callable,
NativeSQLQueryReturn[] queryReturns) { NativeSQLQueryReturn[] queryReturns,
Boolean passDistinctThrough) {
super( super(
name, name,
query.trim(), /* trim done to workaround stupid oracle bug that cant handle whitespaces before a { in a sp */ query.trim(), /* trim done to workaround stupid oracle bug that cant handle whitespaces before a { in a sp */
@ -176,7 +179,8 @@ public class NamedSQLQueryDefinition extends NamedQueryDefinition {
comment, comment,
parameterTypes, parameterTypes,
firstResult, firstResult,
maxResults maxResults,
passDistinctThrough
); );
this.resultSetRef = resultSetRef; this.resultSetRef = resultSetRef;
this.querySpaces = querySpaces; this.querySpaces = querySpaces;
@ -219,7 +223,8 @@ public class NamedSQLQueryDefinition extends NamedQueryDefinition {
getResultSetRef(), getResultSetRef(),
getQuerySpaces(), getQuerySpaces(),
isCallable(), isCallable(),
getQueryReturns() getQueryReturns(),
getPassDistinctThrough()
); );
} }

View File

@ -149,6 +149,11 @@ public class NamedSQLQueryDefinitionBuilder extends NamedQueryDefinitionBuilder
return (NamedSQLQueryDefinitionBuilder) super.setMaxResults( maxResults ); return (NamedSQLQueryDefinitionBuilder) super.setMaxResults( maxResults );
} }
@Override
public NamedSQLQueryDefinitionBuilder setPassDistinctThrough(Boolean passDistinctThrough) {
return (NamedSQLQueryDefinitionBuilder) super.setPassDistinctThrough( passDistinctThrough );
}
@Override @Override
public NamedSQLQueryDefinition createNamedQueryDefinition() { public NamedSQLQueryDefinition createNamedQueryDefinition() {
return new NamedSQLQueryDefinition( return new NamedSQLQueryDefinition(
@ -168,7 +173,8 @@ public class NamedSQLQueryDefinitionBuilder extends NamedQueryDefinitionBuilder
resultSetRef, resultSetRef,
querySpacesCopy(), querySpacesCopy(),
callable, callable,
queryReturns queryReturns,
passDistinctThrough
); );
} }

View File

@ -62,6 +62,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.transaction.internal.TransactionImpl; import org.hibernate.engine.transaction.internal.TransactionImpl;
import org.hibernate.engine.transaction.spi.TransactionImplementor; import org.hibernate.engine.transaction.spi.TransactionImplementor;
import org.hibernate.id.uuid.StandardRandomStrategy; import org.hibernate.id.uuid.StandardRandomStrategy;
import org.hibernate.jpa.QueryHints;
import org.hibernate.jpa.internal.util.FlushModeTypeHelper; import org.hibernate.jpa.internal.util.FlushModeTypeHelper;
import org.hibernate.jpa.spi.CriteriaQueryTupleTransformer; import org.hibernate.jpa.spi.CriteriaQueryTupleTransformer;
import org.hibernate.jpa.spi.HibernateEntityManagerImplementor; import org.hibernate.jpa.spi.HibernateEntityManagerImplementor;
@ -695,6 +696,9 @@ public abstract class AbstractSharedSessionContract implements SharedSessionCont
if ( nqd.getFlushMode() != null ) { if ( nqd.getFlushMode() != null ) {
query.setHibernateFlushMode( nqd.getFlushMode() ); query.setHibernateFlushMode( nqd.getFlushMode() );
} }
if ( nqd.getPassDistinctThrough() != null ) {
query.setHint( QueryHints.HINT_PASS_DISTINCT_THROUGH, nqd.getPassDistinctThrough() );
}
} }
@Override @Override

View File

@ -14,6 +14,9 @@ import javax.persistence.Entity;
import javax.persistence.Id; import javax.persistence.Id;
import javax.persistence.ManyToOne; import javax.persistence.ManyToOne;
import javax.persistence.OneToMany; import javax.persistence.OneToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.QueryHint;
import org.hibernate.boot.SessionFactoryBuilder; import org.hibernate.boot.SessionFactoryBuilder;
import org.hibernate.jpa.QueryHints; import org.hibernate.jpa.QueryHints;
@ -34,6 +37,9 @@ import static org.junit.Assert.assertTrue;
@TestForIssue( jiraKey = "HHH-10965" ) @TestForIssue( jiraKey = "HHH-10965" )
public class SelectDistinctHqlTest extends BaseNonConfigCoreFunctionalTestCase { 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; private SQLStatementInterceptor sqlStatementInterceptor;
@Override @Override
@ -137,7 +143,65 @@ public class SelectDistinctHqlTest extends BaseNonConfigCoreFunctionalTestCase {
}); });
} }
@Test
@TestForIssue(jiraKey = "HHH-13780")
public void testNamedQueryDistinctPassThroughTrue() {
doInHibernate( this::sessionFactory, session -> {
sqlStatementInterceptor.getSqlQueries().clear();
List<Person> 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<Person> 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<Person> 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") @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 { public static class Person {
@Id @Id