Пагинация
Пагинация в tarantool-spring-data
Пагинация в tarantool-spring-data поддерживает интерфейсы Slice<T>,Page<T>,Pageable.
Вместо offset-based пагинации, которую не поддерживает tarantool/crud, интерфейсы реализуют
cursor-based пагинацию.
Pageable
Интерфейс Pageable представляет параметры страницы: номер, размер.
Для использования пагинации в tarantool-spring-data интерфейс Pageable был расширен интерфейсом
TarantoolPageable. Теперь настройки страницы дополнительно содержат кортеж-курсор
(параметр after), относительно
которого считывается страница, а также объект направления пагинации PaginationDirection.
Для создания экземпляра Pageable должны использоваться конструкторы класса
TarantoolPageRequest. Есть два варианта создания TarantoolPageable:
new TarantoolPageRequest<>(int pageSize)- постраничная выборка с начала данный вspace. АргументpageSize- размер страницы.new TarantoolPageRequest<T>(int pageNumber, int pageSize, T cursor)- постраничная выборка данных, начиная с некоторой страницы. Здесь нужно учесть соответствие между номером страницы (начинаются с 0) и переданным курсором.
В качестве примера рассмотрим space (таблицу), в котором хранится 100 записей, удовлетворяющих
вашему запросу на постраничную выборку. Вы хотите получать по 10 записей на страницу, начиная со
второй страницы (pageNumber = 1). Для этого стоит передать следующие параметры:
где domainClassCursor - это объект доменного класса (в примере класс Person), который является
кортежем-курсором
(см. параметр after).
Пример класса модели данных:
@NoArgsConstructor
@AllArgsConstructor
@JsonFormat(shape = JsonFormat.Shape.ARRAY)
@JsonIgnoreProperties(ignoreUnknown = true) // for example bucket_id
@Data
@KeySpace("person")
public class Person {
@Id
@JsonProperty("id")
private Integer id;
@Field("is_married")
@JsonProperty("isMarried")
private Boolean isMarried;
@JsonProperty("name")
private String name;
}
Page
При вызове методов с Page выполняются два запроса:
- Запрос на выборку данных. Если данных нет, возвращается
Page<T>сUnpagedpageable. - Если данные есть, создается запрос на вычисление общего количества записей в
space, удовлетворяющих условиям (методcount()). На основе общего количества записей и размера страницы (передан вTarantoolPageRequestпри создании) определяется общее количество страниц (максимальный номер страницы).
Важно: при параллельной работе с постраничной пагинацией и манипулированием данными (например, удаление или добавление записей) значение количества страниц может измениться.
Таким образом:
- При движении вперед страница существует (не пустая, и
Pageableне являетсяUnpaged), если получены данные и номер страницы не превышает максимальный номер. - При движении назад страница существует, если получены данные и
pageNumber >= 0.
Slice
При вызове методов с Slice запрашивается n + 1 записей, где n - размер
среза. Если получено n + 1 записей, то следующий срез существует.
Таким образом:
- При движении вперед срез существует (не пустой, и
Pageableне являетсяUnpaged), если получены данные. - При движении назад срез существует, если получены данные и
pageNumber >= 0.
Особенности при несоответствии кортежа-курсора и номера страницы
Рассмотрим пример, когда кортеж-курсор соответствует кортежу, после которого начинается страница
(см. параметр after) с номером 0
(pageNumber = 0 (т.е. передан null)), а в конструкторе TarantoolPageRequest указан номер
для другой страницы, например второй (pageNumber = 1):
(0) (1) (2) (3)
Реальное разбиение данных в space: |-----||-----||-----||-----|
[0] [1] [2] [3]
Разбиение, исходя из переданных параметров: |-----||-----||-----||-----|
(вперед)
|------------------->
[0] [1] [2] [3]
Page: |--X--||-----||-----||-----|
<--------------------------|
(назад)
(вперед)
|------------------------->
[0] [1] [2] [3] [4](3)
Slice: |--X--||-----||-----||-----||-----|
<---------------------------------|
(назад)
В данном примере происходит следующее:
Максимальный номер страницы - [3] (ваша нумерация). Он соответствует странице с номером (2)
при реальном разбиении данных.
Page:- При движении вперед последняя существующая страница (не пустая, и
Pageableне являетсяUnpaged) будет иметь номер[3]((2)- при реальном разбиении на страницы). - При движении назад последняя существующая страница будет иметь номер
[1], но при этом методhasPrevious()вернет значениеtrueу этой страницы. При дальнейшем движении вы получите одну пустую страницу сUnpagedpageable.
- При движении вперед последняя существующая страница (не пустая, и
Slice:- При движении вперед последний существующий срез (не пустой, и
Pageableне являетсяUnpaged) будет иметь номер[4]((3)- при реальном разбиении на страницы). - При движении назад последний существующий срез будет иметь номер
[1], но при этом методhasPrevious()вернет значениеtrueу этого среза. При дальнейшем движении вы получите один пустой срез сUnpagedpageable.
- При движении вперед последний существующий срез (не пустой, и
Рассмотрим пример, когда кортеж-курсор соответствует кортежу, после которого начинается страница
(см. параметр after) с номером 1
(pageNumber = 1), а в конструкторе TarantoolPageRequest указан номер для другой страницы,
например 1 (pageNumber = 0):
(0) (1) (2) (3)
Реальное разбиение данных в БД: |-----||-----||-----||-----|
[0] [1] [2] [3]
Разбиение, исходя из переданных параметров: |-----||-----||-----||-----|
(вперед)
|-------------------------->
[0] [1] [2] [3]
Page: |-----||-----||-----||--X--|
<-------------------|
(назад)
(вперед)
|------------------->
[0] [1] [2]
Slice: |-----||-----||-----|
<-------------------|
(назад)
В данном примере происходит следующее:
Максимальный номер страницы - [3] (ваша нумерация). Он соответствует номеру несуществующей
страницы при реальном разбиении данных:
-
Slice:- При движении вперед последний существующий срез будет иметь номер
[2]. - При движении назад последний существующий срез будет иметь номер
[0]((1)- при реальном разбиении на страницы).
- При движении вперед последний существующий срез будет иметь номер
-
Page:- При движении вперед последняя существующая страница (не пустая, и
Pageableне являетсяUnpaged) будет иметь номер[2], но при этом методhasNext()вернет значениеtrueу этой страницы. При дальнейшем движении вы получите одну пустую страницу сUnpagedpageable. - При движении назад последняя существующая страница будет иметь номер
[0]((1)- при реальном разбиении на страницы).
- При движении вперед последняя существующая страница (не пустая, и
Работа с производными методами
При работе с производными методами существует особенность использования запросов с пагинацией.
- Если поле-предикат является полем, для которого построен индекс, то поиск элементов будет происходить по этому индексу. Это означает, что при движении вперед возвращаемые на страницах записи будут идти в порядке возрастания индекса, при движении назад в порядке убывания индекса (используйте средства Java для сортировки элементов страниц).
- Если поле-предикат не является полем, для которого построен индекс, то поиск элементов будет происходить по первичному индексу. Возвращаемые на страницах записи будут идти в порядке возрастания первичного индекса вне зависимости от направления пагинации.
Важно
- Соблюдайте соответствие номера страницы и передаваемого курсора. В ином случае возможны пустые
страницы с
unpagedpageable. Если не уверены в правильности передачи курсора вместе с номером страницы, то используйте конструкторnew TarantoolPageRequest<>(int pageSize), который позволяет пройтись от начала данных и не имеет недостатков присущих пагинации, начинающейся с произвольной страницы. - Если вы работаете на версии Spring Data 3.1.x или выше, используйте
Scroll API для реализации запросов с пагинацией вместо
SliceиPage.