HHH-16815 more convenient pagination via Query API

- add Page convenience class
- add Query.paginate(int, int)
- add Query.paginate(Page)
This commit is contained in:
Gavin King 2023-06-28 14:17:44 +02:00
parent f7e12d49ed
commit 1e46146b54
8 changed files with 201 additions and 84 deletions

View File

@ -900,7 +900,7 @@ Unfortunately, there's no way to do this using JPA's `TypedQuery` interface.
| `setFirstResult()` | Set an offset on the results returned by a query | ✔ | `setFirstResult()` | Set an offset on the results returned by a query | ✔
| `ascending()` | Add a field to use to order the results | ✖ | `ascending()` | Add a field to use to order the results | ✖
| `descending()` | Add a field to use to order the results | ✖ | `descending()` | Add a field to use to order the results | ✖
| `unordered()` | Clear any current ordering | ✖ | `clearOrder()` | Clear any current ordering | ✖
|=== |===
[[projection-lists]] [[projection-lists]]

View File

@ -0,0 +1,63 @@
/*
* 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;
/**
* Identifies a page of query results by {@linkplain #size page size}
* and {@linkplain #number page number}.
* <p>
* This is a convenience class which allows a reference to a page of
* results to be passed around the system before being applied to
* a {@link Query} by calling {@link Query#paginate(Page)}.
*
* @see Query#paginate(Page)
*
* @since 6.3
*
* @author Gavin King
*/
public class Page {
private final int size;
private final int number;
public int getSize() {
return size;
}
public int getNumber() {
return number;
}
public int getMaxResults() {
return size;
}
public int getFirstResult() {
return size*number;
}
public Page(int size, int number) {
this.size = size;
this.number = number;
}
public static Page first(int size) {
return new Page(0, size);
}
public Page next() {
return new Page( size, number+1 );
}
public Page previous() {
return new Page( size, number+1 );
}
public Page first() {
return first( size );
}
}

View File

@ -290,7 +290,6 @@ public interface Query<R> extends SelectionQuery<R>, MutationQuery, TypedQuery<R
* @apiNote This method calls {@link #applyGraph(RootGraph, GraphSemantic)} * @apiNote This method calls {@link #applyGraph(RootGraph, GraphSemantic)}
* using {@link GraphSemantic#LOAD} as the semantic. * using {@link GraphSemantic#LOAD} as the semantic.
*/ */
@SuppressWarnings("UnusedDeclaration")
default Query<R> applyLoadGraph(@SuppressWarnings("rawtypes") RootGraph graph) { default Query<R> applyLoadGraph(@SuppressWarnings("rawtypes") RootGraph graph) {
return applyGraph( graph, GraphSemantic.LOAD ); return applyGraph( graph, GraphSemantic.LOAD );
} }
@ -890,6 +889,20 @@ public interface Query<R> extends SelectionQuery<R>, MutationQuery, TypedQuery<R
@Override @Override
Query<R> setFirstResult(int startPosition); Query<R> setFirstResult(int startPosition);
@Override
default Query<R> paginate(int pageSize, int pageNumber) {
setFirstResult( pageNumber * pageSize );
setMaxResults( pageSize );
return this;
}
@Override
default Query<R> paginate(Page page) {
setMaxResults( page.getMaxResults() );
setFirstResult( page.getFirstResult() );
return this;
}
@Override @Override
Query<R> setHint(String hintName, Object value); Query<R> setHint(String hintName, Object value);

View File

@ -316,16 +316,37 @@ public interface SelectionQuery<R> extends CommonQueryContract {
/** /**
* The first row position to return from the query results. Applied * The first row position to return from the query results. Applied
* to the SQL query * to the SQL query.
*/ */
int getFirstResult(); int getFirstResult();
/** /**
* Set the first row position to return from the query results. Applied * Set the first row position to return from the query results. Applied
* to the SQL query * to the SQL query.
*/ */
SelectionQuery<R> setFirstResult(int startPosition); SelectionQuery<R> setFirstResult(int startPosition);
/**
* Set the page of results to return.
*
* @param pageNumber the page to return, where pages are numbered from zero
* @param pageSize the number of results per page
*
* @since 6.3
*/
@Incubating
SelectionQuery<R> paginate(int pageSize, int pageNumber);
/**
* Set the {@linkplain Page page} of results to return.
*
* @see Page
*
* @since 6.3
*/
@Incubating
SelectionQuery<R> paginate(Page page);
/** /**
* Obtain the {@link CacheMode} in effect for this query. By default, * Obtain the {@link CacheMode} in effect for this query. By default,
* the query inherits the {@link CacheMode} of the session from which * the query inherits the {@link CacheMode} of the session from which
@ -510,6 +531,8 @@ public interface SelectionQuery<R> extends CommonQueryContract {
* *
* @param sorts one or more instances of {@link Sort} * @param sorts one or more instances of {@link Sort}
* *
* @see Sort
*
* @since 6.3 * @since 6.3
*/ */
@Incubating @Incubating

View File

@ -1,3 +1,9 @@
/*
* 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; package org.hibernate.query;
import jakarta.persistence.metamodel.SingularAttribute; import jakarta.persistence.metamodel.SingularAttribute;
@ -18,77 +24,77 @@ import jakarta.persistence.metamodel.SingularAttribute;
* @since 6.3 * @since 6.3
*/ */
public class Sort<X> { public class Sort<X> {
private final SortOrder order; private final SortOrder order;
private final SingularAttribute<X,?> attribute; private final SingularAttribute<X,?> attribute;
private final Class<X> entityClass; private final Class<X> entityClass;
private final String attributeName; private final String attributeName;
public Sort(SortOrder order, SingularAttribute<X, ?> attribute) { public Sort(SortOrder order, SingularAttribute<X, ?> attribute) {
this.order = order; this.order = order;
this.attribute = attribute; this.attribute = attribute;
this.attributeName = attribute.getName(); this.attributeName = attribute.getName();
this.entityClass = attribute.getDeclaringType().getJavaType(); this.entityClass = attribute.getDeclaringType().getJavaType();
} }
public Sort(SortOrder order, Class<X> entityClass, String attributeName) { public Sort(SortOrder order, Class<X> entityClass, String attributeName) {
this.order = order; this.order = order;
this.entityClass = entityClass; this.entityClass = entityClass;
this.attributeName = attributeName; this.attributeName = attributeName;
this.attribute = null; this.attribute = null;
} }
public static <T> Sort<T> asc(SingularAttribute<T,?> attribute) { public static <T> Sort<T> asc(SingularAttribute<T,?> attribute) {
return new Sort<>(SortOrder.ASCENDING, attribute); return new Sort<>(SortOrder.ASCENDING, attribute);
} }
public static <T> Sort<T> desc(SingularAttribute<T,?> attribute) { public static <T> Sort<T> desc(SingularAttribute<T,?> attribute) {
return new Sort<>(SortOrder.ASCENDING, attribute); return new Sort<>(SortOrder.ASCENDING, attribute);
} }
public static <T> Sort<T> asc(Class<T> entityClass, String attributeName) { public static <T> Sort<T> asc(Class<T> entityClass, String attributeName) {
return new Sort<>( SortOrder.ASCENDING, entityClass, attributeName ); return new Sort<>( SortOrder.ASCENDING, entityClass, attributeName );
} }
public static <T> Sort<T> desc(Class<T> entityClass, String attributeName) { public static <T> Sort<T> desc(Class<T> entityClass, String attributeName) {
return new Sort<>( SortOrder.ASCENDING, entityClass, attributeName ); return new Sort<>( SortOrder.ASCENDING, entityClass, attributeName );
} }
public SortOrder getOrder() { public SortOrder getOrder() {
return order; return order;
} }
public SingularAttribute<X, ?> getAttribute() { public SingularAttribute<X, ?> getAttribute() {
return attribute; return attribute;
} }
public Class<X> getEntityClass() { public Class<X> getEntityClass() {
return entityClass; return entityClass;
} }
public String getAttributeName() { public String getAttributeName() {
return attributeName; return attributeName;
} }
@Override @Override
public String toString() { public String toString() {
return attributeName + " " + order; return attributeName + " " + order;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if ( o instanceof Sort ) { if ( o instanceof Sort ) {
Sort<?> that = (Sort<?>) o; Sort<?> that = (Sort<?>) o;
return that.order == order return that.order == order
&& that.attributeName.equals(attributeName) && that.attributeName.equals(attributeName)
&& that.entityClass.equals(entityClass); && that.entityClass.equals(entityClass);
} }
else { else {
return false; return false;
} }
} }
@Override @Override
public int hashCode() { public int hashCode() {
return attributeName.hashCode() + entityClass.hashCode(); return attributeName.hashCode() + entityClass.hashCode();
} }
} }

View File

@ -118,7 +118,6 @@ public abstract class AbstractQuery<R>
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// QueryOptions handling // QueryOptions handling
@Override @Override
public QueryImplementor<R> setHint(String hintName, Object value) { public QueryImplementor<R> setHint(String hintName, Object value) {
super.setHint( hintName, value ); super.setHint( hintName, value );

View File

@ -618,13 +618,10 @@ public abstract class AbstractSelectionQuery<R>
@Override @Override
public SelectionQuery<R> setFirstResult(int startPosition) { public SelectionQuery<R> setFirstResult(int startPosition) {
getSession().checkOpen(); getSession().checkOpen();
if ( startPosition < 0 ) { if ( startPosition < 0 ) {
throw new IllegalArgumentException( "first-result value cannot be negative : " + startPosition ); throw new IllegalArgumentException( "first-result value cannot be negative : " + startPosition );
} }
getQueryOptions().getLimit().setFirstRow( startPosition ); getQueryOptions().getLimit().setFirstRow( startPosition );
return this; return this;
} }

View File

@ -35,7 +35,7 @@ import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.graph.spi.AppliedGraph; import org.hibernate.graph.spi.AppliedGraph;
import org.hibernate.internal.util.collections.IdentitySet; import org.hibernate.internal.util.collections.IdentitySet;
import org.hibernate.query.BindableType; import org.hibernate.query.BindableType;
import org.hibernate.query.Query; import org.hibernate.query.Page;
import org.hibernate.query.QueryLogging; import org.hibernate.query.QueryLogging;
import org.hibernate.query.QueryParameter; import org.hibernate.query.QueryParameter;
import org.hibernate.query.SelectionQuery; import org.hibernate.query.SelectionQuery;
@ -279,6 +279,37 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R>
return hql; return hql;
} }
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// convenience methods
@Override
public SelectionQuery<R> paginate(int pageSize, int pageNumber) {
setFirstResult( pageNumber * pageSize );
setMaxResults( pageSize );
return this;
}
@Override
public SelectionQuery<R> paginate(Page page) {
setMaxResults( page.getMaxResults() );
setFirstResult( page.getFirstResult() );
return this;
}
@Override @SafeVarargs
public final SelectionQuery<R> sort(Sort<? super R>... sorts) {
for (Sort<? super R> sort: sorts) {
SingularAttribute<? super R,?> attribute = sort.getAttribute();
if ( attribute == null ) {
attribute =
getSession().getFactory().getMetamodel()
.entity( sort.getEntityClass() )
.getSingularAttribute( sort.getAttributeName() );
}
sort( sort.getOrder(), attribute );
}
return this;
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// execution // execution
@ -578,21 +609,6 @@ public class SqmSelectionQueryImpl<R> extends AbstractSelectionQuery<R>
return null; return null;
} }
@Override @SafeVarargs
public final SqmSelectionQuery<R> sort(Sort<? super R>... sorts) {
for (Sort<? super R> sort: sorts) {
SingularAttribute<? super R,?> attribute = sort.getAttribute();
if ( attribute == null ) {
attribute =
getSession().getFactory().getMetamodel()
.entity( sort.getEntityClass() )
.getSingularAttribute( sort.getAttributeName() );
}
sort( sort.getOrder(), attribute );
}
return this;
}
@Override @Override
public SqmSelectionQuery<R> ascending(int element) { public SqmSelectionQuery<R> ascending(int element) {
addOrdering( element, SortOrder.ASCENDING ); addOrdering( element, SortOrder.ASCENDING );