Interface KeysetAwareSlice<T>

Type Parameters:
T - the type of elements in this slice
All Superinterfaces:
Iterable<T>, Slice<T>, Streamable<T>
All Known Subinterfaces:
KeysetAwarePage<T>

public interface KeysetAwareSlice<T> extends Slice<T>

Keyset pagination is a form of pagination that aims to reduce the possibility of missed or duplicate results by making the request for each subsequent page relative to the observed values of entity properties from the current page. This list of values is referred to as the keyset and only includes values of entity properties that are in the sort criteria of the repository method. The combination of sort criteria must uniquely identify each entity. The keyset values can be from the last entity (for pagination in a forward direction) or first entity on the page (if requesting pages in a reverse direction), or can be any other desired list of values which serve as a new starting point. Keyset pagination also has the potential to improve performance by avoiding the fetching and ordering of results from prior pages because these become non-matching.

To use keyset pagination, define a repository method with return value of KeysetAwareSlice or KeysetAwarePage and which accepts a special parameter (after the normal query parameters) that is a Pageable. For example,

 @OrderBy("lastName")
 @OrderBy("firstName")
 @OrderBy("id")
 KeysetAwareSlice<Employee> findByHoursWorkedGreaterThan(int hours, Pageable pagination);
 

You can use a normal Pageable to request an initial page,

 page = employees.findByHoursWorkedGreaterThan(1500, Pageable.ofSize(50));
 

For subsequent pages, you can request pagination relative to the end of the current page as follows,

 page = employees.findByHoursWorkedGreaterThan(1500, page.nextPageable());
 

Because the page is keyset aware, the Pageable that it returns from the call to nextPageable() above is based upon a keyset from that page to use as a starting point after which the results for the next page are to be found.

You can also construct a Pageable with a Cursor directly, which allows you to make it relative to a specific list of values. The number and order of values must match that of the OrderBy annotations, Pageable.sortBy(Sort...) or Pageable.sortBy(Iterable) parameters, or OrderBy name pattern of the repository method. For example,

 Employee emp = ...
 Pageable pagination = Pageable.ofSize(50).afterKeyset(emp.lastName, emp.firstName, emp.id);
 page = employees.findByHoursWorkedGreaterThan(1500, pagination);
 

By making the query for the next page relative to observed values, not a numerical position, keyset pagination is less vulnerable to changes that are made to data in between page requests. Adding or removing entities is possible without causing unexpected missed or duplicate results. Keyset pagination does not prevent misses and duplicates if the entity properties which are the sort criteria for existing entities are modified or if an entity is re-added with different sort criteria after having previously been removed.

Keyset Pagination with @Query

Keyset pagination involves generating and appending to the query additional query conditions for the keyset properties. In order for that to be possible, a user-provided JPQL query must end with a WHERE clause to which additional conditions can be appended. Enclose the entire conditional expression of the WHERE clause in parenthesis. Sort criteria must be specified independently from the user-provided query, either with the OrderBy annotation or Pageable.sortBy(Sort...) or Pageable.sortBy(Iterable) parameters. For example,

 @Query("SELECT o FROM Customer o WHERE (o.ordersPlaced >= ?1 OR o.totalSpent >= ?2)")
 @OrderBy("zipcode")
 @OrderBy("birthYear")
 @OrderBy("id")
 KeysetAwareSlice<Customer> getTopBuyers(int minOrders, float minSpent, Pageable pagination);
 

Page Numbers and Totals

Page numbers, total numbers of elements across all pages, and total count of pages are not accurate when keyset pagination is used and should not be relied upon.

Database Support for Keyset Pagination

A repository method with return type of KeysetAwareSlice or KeysetAwarePage must raise UnsupportedOperationException if the database is incapable of keyset pagination.

  • Method Details

    • getKeysetCursor

      Pageable.Cursor getKeysetCursor(int index)
      Returns a Cursor for keyset values at the specified position.
      Parameters:
      index - position (0 is first) of a result on the page.
      Returns:
      cursor for keyset values at the specified position.
    • nextPageable

      Pageable nextPageable()

      Returns pagination information for requesting the next page in a forward direction from the current page. This method computes a keyset from the last entity of the current page and includes the keyset in the pagination information so that it can be used to obtain the next slice in a forward direction according to the sort criteria and relative to that entity.

      Specified by:
      nextPageable in interface Slice<T>
      Returns:
      pagination information for requesting the next page, or null if the current page is empty or if it is known that there is not a next page.
    • previousPageable

      Pageable previousPageable()

      Returns pagination information for requesting the previous page in a reverse direction from the current page. This method computes a keyset from the first entity of the current page and includes the keyset in the pagination information so that it can be used to obtain the previous slice in a reverse direction to the sort criteria and relative to that entity. Within a single page, results are not reversed and remain ordered according to the sort criteria.

      Page numbers are not accurate and should not be relied upon when using keyset pagination. Jakarta Data providers should aim to at least avoid returning negative or 0 as page numbers when traversing pages in the reverse direction (this might otherwise occur when matching entities are added prior to the first page and the previous page is requested) by assigning a page number of 1 to such pages. This means that there can be multiple consecutive pages numbered 1 and that currentPage.previousPageable().next().page() cannot be relied upon to return a page number that is equal to the current page number.

      Returns:
      pagination information for requesting the previous page, or null if the current page is empty or if it is known that there is not a previous page.