HHH-17779 support for key-based pagination

change the API slightly
This commit is contained in:
Gavin King 2024-02-26 16:53:00 +01:00
parent b9e01fec4f
commit dbd15fd690
10 changed files with 67 additions and 45 deletions

View File

@ -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.KeyedPage;
import org.hibernate.query.KeyedResultList;
import org.hibernate.query.Order;
import org.hibernate.query.OutputableType;
@ -971,7 +972,7 @@ public class ProcedureCallImpl<R>
}
@Override
public KeyedResultList<R> getKeyedResultList() {
public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
throw new UnsupportedOperationException("getKeyedResultList() not implemented for ProcedureCall/StoredProcedureQuery");
}

View File

@ -6,6 +6,8 @@
*/
package org.hibernate.query;
import org.hibernate.Incubating;
import java.util.List;
import static java.util.Collections.unmodifiableList;
@ -13,11 +15,35 @@ import static java.util.Collections.unmodifiableList;
/**
* Support for pagination based on a unique key of the result
* set instead of the {@link Page#getFirstResult() offset}.
* <p>
* A specification for an initial page may be obtained from
* an instance of {@link Page}.
* <pre>
* KeyedPage&lt;Book&gt; firstPage = Page.first(10).keyedBy(asc(Book_.isbn)));
* </pre>
* A {@link KeyedResultList} then may be obtained by calling
* {@link SelectionQuery#getKeyedResultList(KeyedPage)}.
* <pre>
* KeyedResultList results =
* session.createQuery("from Book", Book.class)
* .getKeyedResultList(firstPage);
* </pre>
* The following page may be obtained from {@link KeyedResultList#getNextPage()}.
* <pre>
* KeyedPage&lt;Book&gt; nextPage = results.getNextPage();
* KeyedResultList moreResults =
* session.createQuery("from Book", Book.class)
* .getKeyedResultList(nextPage);
* </pre>
*
* @since 6.5
*
* @see SelectionQuery#getKeyedResultList(KeyedPage)
* @see KeyedResultList
*
* @author Gavin King
*/
@Incubating
public class KeyedPage<R> {
private final List<Order<? super R>> keyDefinition;
private final Page page;
@ -27,7 +53,7 @@ public class KeyedPage<R> {
this( keyDefinition, page, null );
}
public KeyedPage(List<Order<? super R>> keyDefinition, Page page, List<Comparable<?>> key) {
KeyedPage(List<Order<? super R>> keyDefinition, Page page, List<Comparable<?>> key) {
this.page = page;
this.keyDefinition = unmodifiableList(keyDefinition);
this.key = key;
@ -48,4 +74,8 @@ public class KeyedPage<R> {
public List<Comparable<?>> getKey() {
return key;
}
public static <R> KeyedPage<R> forKey(List<Order<? super R>> keyDefinition, Page page, List<Comparable<?>> key) {
return new KeyedPage<>( keyDefinition, page, key );
}
}

View File

@ -6,16 +6,25 @@
*/
package org.hibernate.query;
import org.hibernate.Incubating;
import java.util.List;
/**
* Support for pagination based on a unique key of the result
* set instead of the {@link Page#getFirstResult() offset}.
* An instance of this class represent a page of results returned
* by {@link SelectionQuery#getKeyedResultList(KeyedPage)}. The
* actual query results are held in {@link #getResultList()}.
*
* @since 6.5
*
* @see KeyedPage
* @see SelectionQuery#getKeyedResultList(KeyedPage)
*
* @author Gavin King
*/
@Incubating
public class KeyedResultList<R> {
private final List<R> resultList;
private final KeyedPage<R> page;

View File

@ -86,6 +86,9 @@ public class Page {
return first( size );
}
public <R> KeyedPage<R> keyedBy(Order<? super R> keyDefinition) {
return new KeyedPage<>( List.of(keyDefinition), this );
}
public <R> KeyedPage<R> keyedBy(List<Order<? super R>> keyDefinition) {
return new KeyedPage<>( keyDefinition, this );
}

View File

@ -903,11 +903,6 @@ public interface Query<R> extends SelectionQuery<R>, MutationQuery, TypedQuery<R
return this;
}
@Override
default SelectionQuery<R> setPage(KeyedPage<R> page) {
throw new UnsupportedOperationException("keyed paging not supported");
}
@Override
Query<R> setHint(String hintName, Object value);

View File

@ -220,8 +220,23 @@ public interface SelectionQuery<R> extends CommonQueryContract {
@Incubating
long getResultCount();
/**
* Execute the query and return the results for the given
* {@linkplain KeyedPage page}, using key-based pagination.
*
* @param page the key-based specification of the page as
* an instance of {@link KeyedPage}
*
* @return the query results and the key of the next page
* as an instance of {@link KeyedResultList}
*
* @since 6.5
*
* @see KeyedPage
* @see KeyedResultList
*/
@Incubating
KeyedResultList<R> getKeyedResultList();
KeyedResultList<R> getKeyedResultList(KeyedPage<R> page);
SelectionQuery<R> setHint(String hintName, Object value);
@ -398,16 +413,6 @@ public interface SelectionQuery<R> extends CommonQueryContract {
@Incubating
SelectionQuery<R> setPage(Page page);
/**
* Set the {@linkplain KeyedPage keyed page} of results to return.
*
* @see KeyedPage
*
* @since 6.5
*/
@Incubating
SelectionQuery<R> setPage(KeyedPage<R> page);
/**
* Obtain the {@link CacheMode} in effect for this query. By default,
* the query inherits the {@link CacheMode} of the session from which

View File

@ -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.KeyedPage;
import org.hibernate.query.KeyedResultList;
import org.hibernate.query.NativeQuery;
import org.hibernate.query.Order;
@ -639,7 +640,7 @@ public class NativeQueryImpl<R>
}
@Override
public KeyedResultList<R> getKeyedResultList() {
public KeyedResultList<R> getKeyedResultList(KeyedPage<R> page) {
throw new UnsupportedOperationException("native queries do not support key-based pagination");
}

View File

@ -52,8 +52,6 @@ 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);
}
@ -147,12 +145,6 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
return this;
}
@Override
public SelectionQuery<R> setPage(KeyedPage<R> page) {
keyedPage = page;
return this;
}
static class KeyedResult<R> {
final R result;
final List<Comparable<?>> key;
@ -220,16 +212,16 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
// setParameter( parameter, keyValue );
switch ( key.getDirection() ) {
case ASCENDING:
return builder.greaterThan( path, builder.literal(keyValue) );
return builder.greaterThan( path, keyValue );
case DESCENDING:
return builder.lessThan( path, builder.literal(keyValue) );
return builder.lessThan( path, keyValue );
default:
throw new AssertionFailure("Unrecognized key direction");
}
}
@Override
public KeyedResultList<R> getKeyedResultList() {
public KeyedResultList<R> getKeyedResultList(KeyedPage<R> keyedPage) {
if ( keyedPage == null ) {
throw new IllegalStateException( "KeyedPage was not set" );
}
@ -254,7 +246,7 @@ abstract class AbstractSqmSelectionQuery<R> extends AbstractSelectionQuery<R> {
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) );
keyedPage, KeyedPage.forKey( keyDefinition, page.next(), keyOfLastResult ) );
}
public abstract Class<R> getExpectedResultType();

View File

@ -48,7 +48,6 @@ import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.BindableType;
import org.hibernate.query.IllegalQueryOperationException;
import org.hibernate.query.ImmutableEntityUpdateQueryHandlingMode;
import org.hibernate.query.KeyedPage;
import org.hibernate.query.Order;
import org.hibernate.query.Page;
import org.hibernate.query.Query;
@ -923,11 +922,6 @@ public class QuerySqmImpl<R>
return this;
}
@Override
public Query<R> setPage(KeyedPage<R> page) {
super.setPage(page);
return this;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// hints

View File

@ -25,12 +25,10 @@ import org.hibernate.ScrollMode;
import org.hibernate.ScrollableResults;
import org.hibernate.graph.GraphSemantic;
import org.hibernate.query.BindableType;
import org.hibernate.query.KeyedPage;
import org.hibernate.query.Order;
import org.hibernate.query.Page;
import org.hibernate.query.ParameterMetadata;
import org.hibernate.query.QueryParameter;
import org.hibernate.query.SelectionQuery;
import org.hibernate.query.spi.QueryOptions;
import org.hibernate.query.sqm.tree.SqmStatement;
import org.hibernate.sql.results.spi.ResultsConsumer;
@ -198,12 +196,6 @@ public abstract class DelegatingSqmSelectionQueryImplementor<R> implements SqmSe
return this;
}
@Override
public SelectionQuery<R> setPage(KeyedPage<R> page) {
getDelegate().setPage( page );
return this;
}
@Override
public CacheMode getCacheMode() {
return getDelegate().getCacheMode();