HHH-16815 fix issues with query plan cache and ascending()/descending()

This commit is contained in:
Gavin King 2023-06-22 20:41:35 +02:00
parent 28043f8fc4
commit 79c58bbcc8
7 changed files with 178 additions and 14 deletions

View File

@ -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();

View File

@ -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

View File

@ -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) {

View File

@ -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

View File

@ -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() );
}

View File

@ -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 );
}
}

View File

@ -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() {
}
}
}