HHH-17779 support for key-based pagination
This commit is contained in:
parent
5af80551ad
commit
b9e01fec4f
|
@ -45,6 +45,7 @@ import org.hibernate.procedure.spi.ParameterStrategy;
|
|||
import org.hibernate.procedure.spi.ProcedureCallImplementor;
|
||||
import org.hibernate.procedure.spi.ProcedureParameterImplementor;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.query.KeyedResultList;
|
||||
import org.hibernate.query.Order;
|
||||
import org.hibernate.query.OutputableType;
|
||||
import org.hibernate.query.Query;
|
||||
|
@ -969,6 +970,11 @@ public class ProcedureCallImpl<R>
|
|||
throw new UnsupportedOperationException( "getResultCount() not implemented for ProcedureCall/StoredProcedureQuery" );
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyedResultList<R> getKeyedResultList() {
|
||||
throw new UnsupportedOperationException("getKeyedResultList() not implemented for ProcedureCall/StoredProcedureQuery");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ScrollableResultsImplementor<R> scroll(ScrollMode scrollMode) {
|
||||
throw new UnsupportedOperationException( "scroll() is not implemented for ProcedureCall/StoredProcedureQuery" );
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
|
||||
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
|
||||
*/
|
||||
package org.hibernate.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Support for pagination based on a unique key of the result
|
||||
* set instead of the {@link Page#getFirstResult() offset}.
|
||||
*
|
||||
* @since 6.5
|
||||
*
|
||||
* @author Gavin King
|
||||
*/
|
||||
public class KeyedResultList<R> {
|
||||
private final List<R> resultList;
|
||||
private final KeyedPage<R> page;
|
||||
private final KeyedPage<R> nextPage;
|
||||
|
||||
public KeyedResultList(List<R> resultList, KeyedPage<R> page, KeyedPage<R> nextPage) {
|
||||
this.resultList = resultList;
|
||||
this.page = page;
|
||||
this.nextPage = nextPage;
|
||||
}
|
||||
|
||||
public List<R> getResultList() {
|
||||
return resultList;
|
||||
}
|
||||
|
||||
public KeyedPage<R> getPage() {
|
||||
return page;
|
||||
}
|
||||
|
||||
public KeyedPage<R> getNextPage() {
|
||||
return nextPage;
|
||||
}
|
||||
}
|
|
@ -220,6 +220,9 @@ public interface SelectionQuery<R> extends CommonQueryContract {
|
|||
@Incubating
|
||||
long getResultCount();
|
||||
|
||||
@Incubating
|
||||
KeyedResultList<R> getKeyedResultList();
|
||||
|
||||
SelectionQuery<R> setHint(String hintName, Object value);
|
||||
|
||||
/**
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.hibernate.jpa.spi.NativeQueryTupleTransformer;
|
|||
import org.hibernate.metamodel.model.domain.BasicDomainType;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.query.BindableType;
|
||||
import org.hibernate.query.KeyedResultList;
|
||||
import org.hibernate.query.NativeQuery;
|
||||
import org.hibernate.query.Order;
|
||||
import org.hibernate.query.ParameterMetadata;
|
||||
|
@ -637,6 +638,11 @@ public class NativeQueryImpl<R>
|
|||
return createCountQueryPlan().executeQuery( context, new SingleResultConsumer<>() );
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyedResultList<R> getKeyedResultList() {
|
||||
throw new UnsupportedOperationException("native queries do not support key-based pagination");
|
||||
}
|
||||
|
||||
protected SelectQueryPlan<R> resolveSelectQueryPlan() {
|
||||
if ( isCacheableQuery() ) {
|
||||
final QueryInterpretationCache.Key cacheKey = generateSelectInterpretationsKey( resultSetMapping );
|
||||
|
|
|
@ -14,6 +14,7 @@ import org.hibernate.graph.spi.AppliedGraph;
|
|||
import org.hibernate.query.IllegalQueryOperationException;
|
||||
import org.hibernate.query.IllegalSelectQueryException;
|
||||
import org.hibernate.query.KeyedPage;
|
||||
import org.hibernate.query.KeyedResultList;
|
||||
import org.hibernate.query.Order;
|
||||
import org.hibernate.query.Page;
|
||||
import org.hibernate.query.QueryLogging;
|
||||
|
@ -26,6 +27,7 @@ import org.hibernate.query.spi.QueryOptions;
|
|||
import org.hibernate.query.spi.SelectQueryPlan;
|
||||
import org.hibernate.query.sqm.NodeBuilder;
|
||||
import org.hibernate.query.sqm.tree.SqmStatement;
|
||||
import org.hibernate.query.sqm.tree.domain.SqmPath;
|
||||
import org.hibernate.query.sqm.tree.from.SqmFrom;
|
||||
import org.hibernate.query.sqm.tree.from.SqmRoot;
|
||||
import org.hibernate.query.sqm.tree.predicate.SqmPredicate;
|
||||
|
@ -33,9 +35,13 @@ import org.hibernate.query.sqm.tree.predicate.SqmWhereClause;
|
|||
import org.hibernate.query.sqm.tree.select.SqmQuerySpec;
|
||||
import org.hibernate.query.sqm.tree.select.SqmSelectStatement;
|
||||
import org.hibernate.sql.results.internal.TupleMetadata;
|
||||
import org.hibernate.sql.results.spi.ListResultsConsumer;
|
||||
import org.hibernate.sql.results.spi.ListResultsConsumer.UniqueSemantic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static org.hibernate.cfg.QuerySettings.FAIL_ON_PAGINATION_OVER_COLLECTION_FETCH;
|
||||
import static org.hibernate.query.sqm.internal.SqmUtil.sortSpecification;
|
||||
|
@ -46,6 +52,8 @@ import static org.hibernate.query.sqm.tree.SqmCopyContext.noParamCopyContext;
|
|||
*/
|
||||
abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
|
||||
|
||||
private KeyedPage<R> keyedPage;
|
||||
|
||||
AbstractSqmSelectionQuery(SharedSessionContractImplementor session) {
|
||||
super(session);
|
||||
}
|
||||
|
@ -141,48 +149,62 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
|
|||
|
||||
@Override
|
||||
public SelectionQuery<R> setPage(KeyedPage<R> page) {
|
||||
setOrder( page.getKeyDefinition() );
|
||||
setMaxResults( page.getPage().getMaxResults() );
|
||||
if ( page.getKey() == null ) {
|
||||
setFirstResult( page.getPage().getFirstResult() );
|
||||
}
|
||||
else {
|
||||
addRestrictions( page.getKeyDefinition(), page.getKey() );
|
||||
}
|
||||
keyedPage = page;
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addRestrictions(List<Order<? super R>> keyDefinition, List<Comparable<?>> keyValues) {
|
||||
SqmSelectStatement<R> sqm = getSqmSelectStatement();
|
||||
sqm = sqm.copy( noParamCopyContext() );
|
||||
static class KeyedResult<R> {
|
||||
final R result;
|
||||
final List<Comparable<?>> key;
|
||||
public KeyedResult(R result, List<Comparable<?>> key) {
|
||||
this.result = result;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public R getResult() {
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Comparable<?>> getKey() {
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
private SqmSelectStatement<KeyedResult<R>> keyed(List<Order<? super R>> keyDefinition, List<Comparable<?>> keyValues) {
|
||||
@SuppressWarnings("unchecked")
|
||||
final SqmSelectStatement<KeyedResult<R>> sqm =
|
||||
(SqmSelectStatement<KeyedResult<R>>)
|
||||
getSqmSelectStatement().copy( noParamCopyContext() );
|
||||
final NodeBuilder builder = sqm.nodeBuilder();
|
||||
final SqmQuerySpec<R> querySpec = sqm.getQuerySpec();
|
||||
final SqmQuerySpec<?> querySpec = sqm.getQuerySpec();
|
||||
final SqmWhereClause whereClause = querySpec.getWhereClause();
|
||||
final List<? extends JpaSelection<?>> items = querySpec.getSelectClause().getSelectionItems();
|
||||
final List<SqmPath<?>> newItems = new ArrayList<>();
|
||||
if ( items.size() == 1 ) {
|
||||
final JpaSelection<?> selected = items.get(0);
|
||||
for ( int i = 0; i < keyDefinition.size(); i++ ) {
|
||||
// ordering by an attribute of the returned entity
|
||||
if ( selected instanceof SqmRoot ) {
|
||||
whereClause.applyPredicate( keyPredicate(
|
||||
(SqmFrom<?,?>) selected,
|
||||
keyDefinition.get(i),
|
||||
(Comparable) keyValues.get(i),
|
||||
builder)
|
||||
);
|
||||
final Order<? super R> key = keyDefinition.get(i);
|
||||
final SqmFrom<?,?> root = (SqmFrom<?,?>) selected;
|
||||
if ( keyValues != null ) {
|
||||
@SuppressWarnings("rawtypes")
|
||||
final Comparable keyValue = keyValues.get(i);
|
||||
whereClause.applyPredicate( keyPredicate( root, key, keyValue, builder ) );
|
||||
}
|
||||
newItems.add( root.get( key.getAttributeName() ) );
|
||||
}
|
||||
else {
|
||||
throw new IllegalQueryOperationException("Select item was not an entity type");
|
||||
}
|
||||
}
|
||||
sqm.select( builder.construct((Class) KeyedResult.class,
|
||||
asList(selected, builder.construct(List.class, newItems)) ) );
|
||||
return sqm;
|
||||
}
|
||||
else {
|
||||
throw new IllegalQueryOperationException("Query has multiple items in the select list");
|
||||
}
|
||||
// TODO: when the QueryInterpretationCache can handle caching criteria queries,
|
||||
// simply cache the new SQM as if it were a criteria query, and remove this:
|
||||
getQueryOptions().setQueryPlanCachingEnabled( false );
|
||||
setSqmStatement( sqm );
|
||||
}
|
||||
|
||||
private <C extends Comparable<? super C>> SqmPredicate keyPredicate(
|
||||
|
@ -206,6 +228,35 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public KeyedResultList<R> getKeyedResultList() {
|
||||
if ( keyedPage == null ) {
|
||||
throw new IllegalStateException( "KeyedPage was not set" );
|
||||
}
|
||||
final Page page = keyedPage.getPage();
|
||||
final List<Order<? super R>> keyDefinition = keyedPage.getKeyDefinition();
|
||||
final List<Comparable<?>> key = keyedPage.getKey();
|
||||
|
||||
setOrder( keyDefinition );
|
||||
setMaxResults( page.getMaxResults() );
|
||||
if ( key == null ) {
|
||||
setFirstResult( page.getFirstResult() );
|
||||
}
|
||||
else {
|
||||
keyed( keyDefinition, key );
|
||||
}
|
||||
|
||||
final SqmSelectStatement<KeyedResult<R>> sqm = keyed( keyDefinition, key );
|
||||
final ConcreteSqmSelectQueryPlan<KeyedResult<R>> plan =
|
||||
buildConcreteQueryPlan( sqm, null, null, getQueryOptions() );
|
||||
final List<KeyedResult<R>> executed =
|
||||
plan.executeQuery( this, new ListResultsConsumer<>(UniqueSemantic.NONE) );
|
||||
final List<Comparable<?>> keyOfLastResult =
|
||||
executed.isEmpty() ? key : executed.get( executed.size()-1 ).getKey();
|
||||
return new KeyedResultList<>( executed.stream().map(KeyedResult::getResult).collect(toList()),
|
||||
keyedPage, new KeyedPage<>(keyDefinition, page.next(), keyOfLastResult) );
|
||||
}
|
||||
|
||||
public abstract Class<R> getExpectedResultType();
|
||||
|
||||
protected SelectQueryPlan<R> buildSelectQueryPlan() {
|
||||
|
|
Loading…
Reference in New Issue