HHH-16815 fix issues with query plan cache and ascending()/descending()
This commit is contained in:
parent
28043f8fc4
commit
79c58bbcc8
|
@ -471,12 +471,36 @@ public interface SelectionQuery<R> extends CommonQueryContract {
|
|||
*/
|
||||
SelectionQuery<R> setLockMode(String alias, LockMode lockMode);
|
||||
|
||||
/**
|
||||
* If the result type of this query is an entity class, add an attribute
|
||||
* of the entity to be used to order the query results in ascending order.
|
||||
*
|
||||
* @param attribute an attribute of the entity class returned by this query
|
||||
*
|
||||
* @since 6.3
|
||||
*/
|
||||
@Incubating
|
||||
SelectionQuery<R> ascending(SingularAttribute<? super R, ?> attribute);
|
||||
|
||||
/**
|
||||
* If the result type of this query is an entity class, add an attribute
|
||||
* of the entity to be used to order the query results in descending order.
|
||||
*
|
||||
* @param attribute an attribute of the entity class returned by this query
|
||||
*
|
||||
* @since 6.3
|
||||
*/
|
||||
@Incubating
|
||||
SelectionQuery<R> descending(SingularAttribute<? super R, ?> attribute);
|
||||
|
||||
/**
|
||||
* Clear the ordering conditions for this query.
|
||||
*
|
||||
* @see #ascending(SingularAttribute)
|
||||
* @see #descending(SingularAttribute)
|
||||
*
|
||||
* @since 6.3
|
||||
*/
|
||||
@Incubating
|
||||
SelectionQuery<R> unordered();
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ public class QuerySqmImpl<R>
|
|||
private static final CoreMessageLogger LOG = CoreLogging.messageLogger( QuerySqmImpl.class );
|
||||
|
||||
private final String hql;
|
||||
private final SqmStatement<R> sqm;
|
||||
private SqmStatement<R> sqm;
|
||||
|
||||
private final ParameterMetadataImplementor parameterMetadata;
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
|
@ -963,14 +963,15 @@ public class QuerySqmImpl<R>
|
|||
@Override
|
||||
public SqmQueryImplementor<R> addOrdering(SingularAttribute<? super R, ?> attribute, SortOrder order) {
|
||||
if ( sqm instanceof SqmSelectStatement ) {
|
||||
sqm = sqm.copy( SqmCopyContext.simpleContext() );
|
||||
NodeBuilder nodeBuilder = sqm.nodeBuilder();
|
||||
SqmSelectStatement<R> select = (SqmSelectStatement<R>) sqm;
|
||||
List<Order> orders = new ArrayList<>( select.getOrderList() );
|
||||
select.getQuerySpec().getRoots().forEach( root -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
SqmRoot<R> singleRoot = (SqmRoot<R>) root;
|
||||
SqmPath<?> ref = singleRoot.get( attribute );
|
||||
NodeBuilder nodeBuilder = select.nodeBuilder();
|
||||
orders.add( order==SortOrder.ASCENDING ? nodeBuilder.asc(ref) : nodeBuilder.desc(ref) );
|
||||
orders.add( nodeBuilder.sort( ref, order) );
|
||||
|
||||
} );
|
||||
select.orderBy( orders );
|
||||
|
@ -984,6 +985,7 @@ public class QuerySqmImpl<R>
|
|||
@Override
|
||||
public SqmQueryImplementor<R> unordered() {
|
||||
if ( sqm instanceof SqmSelectStatement ) {
|
||||
sqm = sqm.copy( SqmCopyContext.simpleContext() );
|
||||
SqmSelectStatement<R> select = (SqmSelectStatement<R>) sqm;
|
||||
select.getQueryPart().setOrderByClause( null );
|
||||
|
||||
|
@ -991,6 +993,13 @@ public class QuerySqmImpl<R>
|
|||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Order> getOrder() {
|
||||
return sqm instanceof SqmSelectStatement
|
||||
? ((SqmSelectStatement<R>) sqm).getOrderList()
|
||||
: null;
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// hints
|
||||
|
||||
|
|
|
@ -8,8 +8,10 @@ package org.hibernate.query.sqm.internal;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import jakarta.persistence.criteria.Order;
|
||||
import org.hibernate.LockOptions;
|
||||
import org.hibernate.engine.spi.LoadQueryInfluencers;
|
||||
import org.hibernate.query.ResultListTransformer;
|
||||
|
@ -36,6 +38,7 @@ public class SqmInterpretationsKey implements QueryInterpretationCache.Key {
|
|||
|
||||
public interface InterpretationsKeySource extends CacheabilityInfluencers {
|
||||
Class<?> getResultType();
|
||||
List<Order> getOrder();
|
||||
}
|
||||
|
||||
public static SqmInterpretationsKey createInterpretationsKey(InterpretationsKeySource keySource) {
|
||||
|
@ -45,6 +48,7 @@ public class SqmInterpretationsKey implements QueryInterpretationCache.Key {
|
|||
? keySource.getSqmStatement()
|
||||
: keySource.getQueryString(),
|
||||
keySource.getResultType(),
|
||||
keySource.getOrder(),
|
||||
keySource.getQueryOptions().getLockOptions(),
|
||||
keySource.getQueryOptions().getTupleTransformer(),
|
||||
keySource.getQueryOptions().getResultListTransformer(),
|
||||
|
@ -86,6 +90,7 @@ public class SqmInterpretationsKey implements QueryInterpretationCache.Key {
|
|||
|
||||
private final Object query;
|
||||
private final Class<?> resultType;
|
||||
private final List<Order> order;
|
||||
private final LockOptions lockOptions;
|
||||
private final TupleTransformer<?> tupleTransformer;
|
||||
private final ResultListTransformer<?> resultListTransformer;
|
||||
|
@ -94,12 +99,14 @@ public class SqmInterpretationsKey implements QueryInterpretationCache.Key {
|
|||
private SqmInterpretationsKey(
|
||||
Object query,
|
||||
Class<?> resultType,
|
||||
List<Order> order,
|
||||
LockOptions lockOptions,
|
||||
TupleTransformer<?> tupleTransformer,
|
||||
ResultListTransformer<?> resultListTransformer,
|
||||
Collection<String> enabledFetchProfiles) {
|
||||
this.query = query;
|
||||
this.resultType = resultType;
|
||||
this.order = order;
|
||||
this.lockOptions = lockOptions;
|
||||
this.tupleTransformer = tupleTransformer;
|
||||
this.resultListTransformer = resultListTransformer;
|
||||
|
@ -112,6 +119,7 @@ public class SqmInterpretationsKey implements QueryInterpretationCache.Key {
|
|||
query,
|
||||
resultType,
|
||||
// Since lock options are mutable, we need a copy for the cache key
|
||||
order,
|
||||
lockOptions.makeCopy(),
|
||||
tupleTransformer,
|
||||
resultListTransformer,
|
||||
|
@ -135,11 +143,12 @@ public class SqmInterpretationsKey implements QueryInterpretationCache.Key {
|
|||
|
||||
final SqmInterpretationsKey that = (SqmInterpretationsKey) o;
|
||||
return query.equals( that.query )
|
||||
&& areEqual( resultType, that.resultType )
|
||||
&& areEqual( lockOptions, that.lockOptions )
|
||||
&& areEqual( tupleTransformer, that.tupleTransformer )
|
||||
&& areEqual( resultListTransformer, that.resultListTransformer )
|
||||
&& areEqual( enabledFetchProfiles, that.enabledFetchProfiles );
|
||||
&& areEqual( resultType, that.resultType )
|
||||
&& areEqual( order, that.order )
|
||||
&& areEqual( lockOptions, that.lockOptions )
|
||||
&& areEqual( tupleTransformer, that.tupleTransformer )
|
||||
&& areEqual( resultListTransformer, that.resultListTransformer )
|
||||
&& areEqual( enabledFetchProfiles, that.enabledFetchProfiles );
|
||||
}
|
||||
|
||||
private <T> boolean areEqual(T o1, T o2) {
|
||||
|
|
|
@ -88,7 +88,7 @@ import static org.hibernate.query.sqm.internal.SqmInterpretationsKey.createInter
|
|||
public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R>
|
||||
implements SqmSelectionQuery<R>, InterpretationsKeySource {
|
||||
private final String hql;
|
||||
private final SqmSelectStatement<R> sqm;
|
||||
private SqmSelectStatement<R> sqm;
|
||||
|
||||
private final ParameterMetadataImplementor parameterMetadata;
|
||||
private final DomainParameterXref domainParameterXref;
|
||||
|
@ -568,13 +568,14 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R>
|
|||
}
|
||||
|
||||
private void addOrdering(SingularAttribute<? super R, ?> attribute, SortOrder order) {
|
||||
sqm = sqm.copy( SqmCopyContext.simpleContext() );
|
||||
NodeBuilder nodeBuilder = sqm.nodeBuilder();
|
||||
List<Order> orders = new ArrayList<>( sqm.getOrderList() );
|
||||
sqm.getQuerySpec().getRoots().forEach( root -> {
|
||||
@SuppressWarnings("unchecked")
|
||||
SqmRoot<R> singleRoot = (SqmRoot<R>) root;
|
||||
SqmPath<?> ref = singleRoot.get( attribute );
|
||||
NodeBuilder nodeBuilder = sqm.nodeBuilder();
|
||||
orders.add( order==SortOrder.ASCENDING ? nodeBuilder.asc(ref) : nodeBuilder.desc(ref) );
|
||||
orders.add( nodeBuilder.sort( ref, order ) );
|
||||
|
||||
} );
|
||||
sqm.orderBy( orders );
|
||||
|
@ -582,10 +583,16 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R>
|
|||
|
||||
@Override
|
||||
public SqmSelectionQuery<R> unordered() {
|
||||
sqm = sqm.copy( SqmCopyContext.simpleContext() );
|
||||
sqm.getQueryPart().setOrderByClause( null );
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Order> getOrder() {
|
||||
return sqm.getOrderList();
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
// hints
|
||||
|
||||
|
|
|
@ -237,7 +237,6 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Set<ParameterExpression<?>> getParameters() {
|
||||
// At this level, the number of parameters may still be growing as
|
||||
// nodes are added to the Criteria - so we re-calculate this every
|
||||
|
@ -245,8 +244,7 @@ public class SqmSelectStatement<T> extends AbstractSqmSelectQuery<T> implements
|
|||
//
|
||||
// for a "finalized" set of parameters, use `#resolveParameters` instead
|
||||
assert querySource == SqmQuerySource.CRITERIA;
|
||||
final Set<ParameterExpression<?>> sqmParameters = (Set<ParameterExpression<?>>) (Set<?>) getSqmParameters();
|
||||
return sqmParameters.stream()
|
||||
return getSqmParameters().stream()
|
||||
.filter( parameterExpression -> !( parameterExpression instanceof ValueBindJpaCriteriaParameter ) )
|
||||
.collect( Collectors.toSet() );
|
||||
}
|
||||
|
|
|
@ -13,6 +13,8 @@ import org.hibernate.query.criteria.JpaOrder;
|
|||
import org.hibernate.query.sqm.tree.SqmCopyContext;
|
||||
import org.hibernate.query.sqm.tree.expression.SqmExpression;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
*/
|
||||
|
@ -108,4 +110,26 @@ public class SqmSortSpecification implements JpaOrder {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if ( this == o ) {
|
||||
return true;
|
||||
}
|
||||
else if ( !(o instanceof SqmSortSpecification) ) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
// used in SqmInterpretationsKey.equals()
|
||||
SqmSortSpecification that = (SqmSortSpecification) o;
|
||||
return Objects.equals( sortExpression, that.sortExpression )
|
||||
&& sortOrder == that.sortOrder
|
||||
&& nullPrecedence == that.nullPrecedence;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash( sortExpression, sortOrder, nullPrecedence );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package org.hibernate.orm.test.query.order;
|
||||
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.metamodel.SingularAttribute;
|
||||
import org.hibernate.metamodel.model.domain.EntityDomainType;
|
||||
import org.hibernate.testing.orm.junit.DomainModel;
|
||||
import org.hibernate.testing.orm.junit.SessionFactory;
|
||||
import org.hibernate.testing.orm.junit.SessionFactoryScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@SessionFactory
|
||||
@DomainModel(annotatedClasses = OrderTest.Book.class)
|
||||
public class OrderTest {
|
||||
|
||||
@Test void testAscendingDescending(SessionFactoryScope scope) {
|
||||
scope.inTransaction( session -> {
|
||||
session.persist(new Book("9781932394153", "Hibernate in Action"));
|
||||
session.persist(new Book("9781617290459", "Java Persistence with Hibernate"));
|
||||
});
|
||||
EntityDomainType<Book> bookType = scope.getSessionFactory().getJpaMetamodel().findEntityType(Book.class);
|
||||
SingularAttribute<? super Book, ?> title = bookType.findSingularAttribute("title");
|
||||
SingularAttribute<? super Book, ?> isbn = bookType.findSingularAttribute("isbn");
|
||||
scope.inSession(session -> {
|
||||
List<String> titlesAsc = session.createSelectionQuery("from Book", Book.class)
|
||||
.ascending(title)
|
||||
.getResultList()
|
||||
.stream().map(book -> book.title)
|
||||
.collect(toList());
|
||||
assertEquals("Hibernate in Action", titlesAsc.get(0));
|
||||
assertEquals("Java Persistence with Hibernate", titlesAsc.get(1));
|
||||
|
||||
List<String> titlesDesc = session.createSelectionQuery("from Book", Book.class)
|
||||
.descending(title)
|
||||
.getResultList()
|
||||
.stream().map(book -> book.title)
|
||||
.collect(toList());
|
||||
assertEquals("Hibernate in Action", titlesDesc.get(1));
|
||||
assertEquals("Java Persistence with Hibernate", titlesDesc.get(0));
|
||||
|
||||
List<String> isbnAsc = session.createSelectionQuery("from Book", Book.class)
|
||||
.ascending(isbn).descending(title)
|
||||
.getResultList()
|
||||
.stream().map(book -> book.title)
|
||||
.collect(toList());
|
||||
assertEquals("Hibernate in Action", isbnAsc.get(1));
|
||||
assertEquals("Java Persistence with Hibernate", isbnAsc.get(0));
|
||||
|
||||
List<String> isbnDesc = session.createSelectionQuery("from Book", Book.class)
|
||||
.descending(isbn).descending(title)
|
||||
.getResultList()
|
||||
.stream().map(book -> book.title)
|
||||
.collect(toList());
|
||||
assertEquals("Hibernate in Action", isbnDesc.get(0));
|
||||
assertEquals("Java Persistence with Hibernate", isbnDesc.get(1));
|
||||
|
||||
titlesAsc = session.createSelectionQuery("from Book order by isbn asc", Book.class)
|
||||
.ascending(title)
|
||||
.getResultList()
|
||||
.stream().map(book -> book.title)
|
||||
.collect(toList());
|
||||
assertEquals("Hibernate in Action", titlesAsc.get(1));
|
||||
assertEquals("Java Persistence with Hibernate", titlesAsc.get(0));
|
||||
titlesAsc = session.createSelectionQuery("from Book order by isbn asc", Book.class)
|
||||
.unordered()
|
||||
.ascending(title)
|
||||
.getResultList()
|
||||
.stream().map(book -> book.title)
|
||||
.collect(toList());
|
||||
assertEquals("Hibernate in Action", titlesAsc.get(0));
|
||||
assertEquals("Java Persistence with Hibernate", titlesAsc.get(1));
|
||||
});
|
||||
}
|
||||
|
||||
@Entity(name="Book")
|
||||
static class Book {
|
||||
@Id String isbn;
|
||||
String title;
|
||||
|
||||
Book(String isbn, String title) {
|
||||
this.isbn = isbn;
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
Book() {
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue