Что такое пагинация и зачем она нужна
Что проверяют
Знаете ли вы, что пагинация защищает не столько глаза пользователя, сколько базу и память приложения. В Java следом почти всегда идёт вопрос про Spring Data и про то, во что ваш Pageable превращается в SQL.
Как отвечать
Определение короткое: выдача большого набора порциями, страница задаётся размером и позицией. Смысл в предсказуемости, один запрос не вытащит в heap полмиллиона сущностей и не положит сервис.
Способов два. Offset based: смещение и лимит, доступна любая страница по номеру. Keyset, он же курсорный: следующая страница берётся от значения ключа последней показанной строки.
select * from orders
where (created_at, id) < (?, ?)
order by created_at desc, id desc
limit 20Offset проще и даёт нумерацию, но база при большом смещении всё равно читает и отбрасывает пропущенные строки, поэтому глубокие страницы деградируют линейно. Keyset держит постоянное время на любой глубине, но умеет только шаг вперёд и назад, без прыжка на произвольный номер.
Из Spring Data стоит назвать Pageable, разницу между Page и Slice (Page дополнительно выполняет запрос подсчёта, Slice лишь проверяет наличие следующей порции) и Sort, который обязан быть детерминированным.
Где спотыкаются
Пагинируют без устойчивой сортировки. Если в ORDER BY нет уникального последнего поля, база вправе вернуть строки с одинаковым ключом в любом порядке, и на соседних страницах появляются дубли и пропуски. Тайбрейкер по первичному ключу обязателен.
Возвращают Page там, где счётчик не нужен. Подсчёт по большой таблице с фильтрами стоит дороже самой страницы, а если интерфейсу нужна только кнопка «ещё», хватает Slice.
Ловят предупреждение Hibernate про firstResult и maxResults вместе с fetch join коллекции. Ограничить строки на стороне базы в этом случае нельзя, и результат режется в памяти приложения. Лечится двухшаговой выборкой: сначала страница идентификаторов, затем загрузка коллекций по ним.