Пагинация, сортировка и проекции
Возвращайте постраничные результаты с помощью Pageable, динамически сортируйте их и используйте проекции через интерфейсы, чтобы ограничить набор загружаемых столбцов.
«Пагинация, сортировка и проекции» — бесплатный урок Java Academy на CoddyKit. Это урок 4 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения Java Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс Java Academy содержит 4 уроков всего.
Зачем нужна пагинация
Возврат тысяч строк одним запросом напрасно расходует память и пропускную способность. Пагинация загружает по одной странице за раз. Spring Data интегрирует пагинацию с абстракцией Pageable.
Pageable и PageRequest
Создайте Pageable с помощью PageRequest.of(page, size). Передайте его в любой метод репозитория, возвращающий Page<T>.
Pageable pageable = PageRequest.of(0, 20); // page 0, 20 per page
Page<User> page = userRepo.findAll(pageable);
System.out.println("Total: " + page.getTotalElements());
System.out.println("Pages: " + page.getTotalPages());
System.out.println("Content: " + page.getContent().size());Сортировка с PageRequest
Добавьте сортировку в PageRequest с помощью объекта Sort. Объединяйте несколько полей цепочкой Sort.by().and().
Pageable sorted = PageRequest.of(0, 20,
Sort.by(Sort.Direction.DESC, "createdAt")
.and(Sort.by("name")));
Page<User> page = userRepo.findAll(sorted);Срез и Page
Page<T> выполняет запрос COUNT, чтобы вычислить общее число страниц. Slice<T> этого не делает — он лишь сообщает, есть ли следующая страница. Используйте Slice для бесконечной прокрутки, когда общее количество элементов не требуется.
public interface UserRepository extends JpaRepository<User, Long> {
Slice<User> findByStatus(UserStatus status, Pageable pageable);
}Интеграция с Spring MVC
Spring MVC автоматически получает Pageable из параметров запроса: ?page=0&size=20&sort=name,asc. Разбирать параметры вручную не нужно.
@GetMapping("/users")
public Page<UserDto> list(Pageable pageable) {
return userRepo.findAll(pageable).map(UserDto::from);
}
// GET /users?page=0&size=10&sort=name,ascПроекции на основе интерфейсов
Определите интерфейс с методами получения значений, имена которых совпадают с именами полей сущности. Spring Data вернёт только эти столбцы, сократив объём передаваемых данных и скрыв внутреннее устройство сущности.
public interface UserSummary {
Long getId();
String getName();
String getEmail();
}
List<UserSummary> findByStatus(UserStatus status);Проекции на основе классов (DTO)
Используйте запись DTO или класс с конструктором. Spring Data создаст выражение конструктора JPQL для выбранных столбцов.
public record UserDto(Long id, String name, String email) {}
@Query("SELECT new com.example.UserDto(u.id, u.name, u.email) FROM User u WHERE u.status = :s")
List<UserDto> findSummaries(@Param("s") UserStatus status);Динамические проекции
Передайте тип проекции в качестве параметра обобщённого типа методу репозитория, чтобы выбрать проекцию в месте вызова.
public interface UserRepository extends JpaRepository<User, Long> {
<T> List<T> findByStatus(UserStatus status, Class<T> type);
}
// Usage:
List<UserSummary> summaries = repo.findByStatus(ACTIVE, UserSummary.class);
List<User> full = repo.findByStatus(ACTIVE, User.class);Пагинация по ключу (смещение и ключ)
Пагинация со смещением (OFFSET N) замедляется при больших смещениях. Пагинация по ключу (с использованием WHERE id > lastSeenId) имеет сложность O(1) независимо от глубины страницы и лучше подходит для глубокой пагинации.
// Keyset pagination:
@Query("SELECT u FROM User u WHERE u.id > :lastId ORDER BY u.id LIMIT :size")
List<User> findPage(@Param("lastId") Long lastId, @Param("size") int size);Оптимизация подсчёта общего количества
Используйте countQuery в @Query, чтобы задать оптимизированный запрос подсчёта и избежать дорогостоящих JOIN в основном запросе.
@Query(value = "SELECT u FROM User u JOIN FETCH u.orders WHERE u.status = :s",
countQuery = "SELECT COUNT(u) FROM User u WHERE u.status = :s")
Page<User> findActiveWithOrders(@Param("s") UserStatus s, Pageable p);Возврат ResponseEntity с метаданными страницы
Включайте метаданные страницы (totalElements, totalPages, number, size) в REST-ответ, чтобы клиенты знали, как переходить между страницами.
@GetMapping("/users")
public ResponseEntity<Map<String, Object>> list(Pageable p) {
Page<UserDto> page = repo.findAll(p).map(UserDto::from);
return ResponseEntity.ok(Map.of(
"content", page.getContent(),
"totalElements", page.getTotalElements(),
"totalPages", page.getTotalPages()
));
}Быстрая проверка
В чём состоит главное различие между Page и срезом?
Итоги
Используйте Pageable+PageRequest.of() для пагинации со смещением. Spring MVC получает Pageable из параметров запроса. Используйте Slice, чтобы избежать COUNT. Применяйте проекции на основе интерфейсов или DTO, чтобы ограничить набор загружаемых столбцов. Для больших смещений используйте пагинацию по ключу.
Часто задаваемые вопросы
Урок «Пагинация, сортировка и проекции» бесплатный?
Да — полный текст урока «Пагинация, сортировка и проекции» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс Java Academy, подпишись на CoddyKit PRO. Курс Java Academy содержит 4 уроков всего.
Чему я научусь в уроке «Пагинация, сортировка и проекции»?
Возвращайте постраничные результаты с помощью Pageable, динамически сортируйте их и используйте проекции через интерфейсы, чтобы ограничить набор загружаемых столбцов. Ты практикуешь Java Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.
Нужен ли мне опыт, чтобы начать Java Academy?
Предыдущий опыт не требуется. Java Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 4 из 4.
Сколько времени занимает урок «Пагинация, сортировка и проекции»?
Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.
Можно ли писать и запускать код в этом уроке Java Academy?
Да. Каждый урок Java Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.
Все уроки этого курса
- Отображение сущностей с аннотациями JPA
- Репозитории Spring Data и методы запросов
- Связи один-ко-многим и многие-ко-многим
- Пагинация, сортировка и проекции