Как реализовывать ответ с пагинацией
Что проверяют
Умеете ли вы спроектировать контракт ответа, а не только написать LIMIT. Интервьюеру важно, откуда берутся метаданные страницы, сколько стоит их подсчёт и как вы защищаете API от запроса на миллион записей. Следом часто спрашивают, что делать, когда OFFSET на глубоких страницах начинает тормозить.
Как отвечать
В Spring-стеке каркас такой:
- вход: контроллер принимает
Pageable, Spring собирает его изpage,sizeиsort. Страницы нумеруются с нуля, размер по умолчанию 20, потолок задаётspring.data.web.pageable.max-page-size(по умолчанию 2000, для публичного API лучше меньше); - репозиторий:
Page<T> findAll(Pageable)делает два запроса, выборку иcount. Если общее число не нужно, возвращайтеSlice<T>: он берёт на одну запись больше и знает только, есть ли следующая страница; - сортировка: всегда стабильная, с уникальным последним ключом (
Sort.by("createdAt").descending().and(Sort.by("id"))), иначе строки прыгают между страницами; - ответ: DTO, а не сущности и не сырой
PageImpl. С Spring Data 3.3 сериализацияPageImplв JSON сопровождается предупреждением, стабильную структуру даётPagedModelили@EnableSpringDataWebSupport(pageSerializationMode = VIA_DTO). Типичное тело:items,page,size,totalElements,totalPages; - глубокие страницы и ленты: keyset-пагинация через Scroll API (
Window<T>,ScrollPosition.keyset()), клиенту отдаётся непрозрачный курсор.
Без Spring всё то же делается руками: валидация page >= 0 и 1 <= size <= max, запрос с LIMIT/OFFSET или по ключу, отдельный COUNT только там, где он нужен интерфейсу.
Где спотыкаются
Пагинируют запрос с JOIN FETCH по коллекции. Hibernate не может ограничить строки в SQL, поднимает всё в память и режет там, а в логе остаётся лишь предупреждение.
Не ограничивают size. Один запрос с size=100000 нагружает и базу, и сериализацию.
Считают count(*) на каждой странице большой таблицы. На десятках миллионов строк подсчёт дороже самой выборки, и Slice или приблизительная оценка здесь честнее.