페이지 매김, 정렬 및 슬라이스 스트리밍
대규모 데이터 집합과 무한 스크롤 UI를 위해 페이지 단위·정렬·스트리밍 결과 집합을 효율적으로 반환합니다.
페이지 매김, 정렬 및 슬라이스 스트리밍은(는) CoddyKit의 무료 Spring Boot 4 Complete Guide 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Spring Boot 4 Complete Guide 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.
이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.
Why Pagination Matters
Loading an entire table into memory is one of the fastest ways to crash a service. A findAll() on a million-row table builds a giant list, exhausts the heap, and blocks the request thread.
Pagination solves this by fetching results in small chunks (pages). Spring Data JPA gives you first-class support through the Pageable abstraction.
- Page — knows the total element count and total page count.
- Slice — only knows whether a next chunk exists (cheaper).
- Stream — processes rows one-by-one without materializing a list.
In this lesson you will learn when to reach for each one.
The Pageable Parameter
Add a Pageable parameter to any repository method and Spring Data appends LIMIT and OFFSET (or the dialect equivalent) to the generated query automatically.
The method returns a Page<T>, which wraps the content list plus paging metadata.
public interface ProductRepository extends JpaRepository<Product, Long> {
Page<Product> findByCategory(String category, Pageable pageable);
}Building a PageRequest
The concrete implementation of Pageable is PageRequest. You create one with a zero-based page number and a page size.
PageRequest.of(0, 20)— first page, 20 items.PageRequest.of(2, 20)— third page (rows 40-59).
In a controller you usually let Spring resolve it from the request automatically, but you can also build it by hand in a service.
@Service
public class ProductService {
private final ProductRepository repository;
public ProductService(ProductRepository repository) {
this.repository = repository;
}
public Page<Product> firstPage(String category) {
Pageable pageable = PageRequest.of(0, 20);
return repository.findByCategory(category, pageable);
}
}Reading Page Metadata
A Page<T> exposes everything a UI needs to render pagination controls.
getContent()— the rows on this page.getTotalElements()— total matching rows in the table.getTotalPages()— total number of pages.getNumber()— current zero-based page index.hasNext()/hasPrevious()— navigation flags.
Important: computing getTotalElements() requires an extra COUNT(*) query on every call.
Page<Product> page = repository.findByCategory("books", PageRequest.of(0, 20));
List<Product> rows = page.getContent();
long total = page.getTotalElements();
int totalPages = page.getTotalPages();
boolean more = page.hasNext();Sorting with Sort
Sorting is part of Pageable. Pass a Sort object into PageRequest.of(page, size, sort) and Spring Data adds an ORDER BY clause.
Sort.by("price")— ascending by price.Sort.by("price").descending()— descending.- Chain multiple orders for tie-breaking.
Sort sort = Sort.by("price").descending()
.and(Sort.by("name").ascending());
Pageable pageable = PageRequest.of(0, 20, sort);
Page<Product> page = repository.findByCategory("books", pageable);Multi-Field Sort with Order
For finer control over null handling and direction per field, build the Sort from Sort.Order objects.
This is the clearest way to express something like "newest first, then alphabetical, nulls last".
Sort sort = Sort.by(
Sort.Order.desc("createdAt"),
Sort.Order.asc("name").nullsLast()
);
Pageable pageable = PageRequest.of(0, 25, sort);
Page<Product> page = repository.findAll(pageable);Slice vs Page
A Page always runs an extra COUNT(*) to know the total. For an infinite-scroll UI you rarely need the total — you only need to know if there is a next chunk.
Return Slice<T> instead. Spring fetches size + 1 rows internally: if the extra row exists, hasNext() is true. No count query runs, so it is noticeably cheaper on large tables.
Slice.getContent()andSlice.hasNext()work just like Page.Slice.getTotalElements()does not exist.
public interface FeedRepository extends JpaRepository<Post, Long> {
Slice<Post> findByAuthorId(Long authorId, Pageable pageable);
}Consuming a Slice for Infinite Scroll
On the client you keep incrementing the page number while hasNext() stays true. The service stays clean because the repository handles the size + 1 trick.
public Slice<Post> nextChunk(Long authorId, int pageNumber) {
Pageable pageable = PageRequest.of(
pageNumber, 15, Sort.by("createdAt").descending());
Slice<Post> slice = feedRepository.findByAuthorId(authorId, pageable);
if (slice.hasNext()) {
// tell the UI to request pageNumber + 1
}
return slice;
}The Offset Problem
Offset pagination has a hidden cost: to return page 10000 with size 20, the database must scan and discard 200000 rows before returning yours. Deep pages get slower and slower.
Keyset (cursor) pagination avoids this. Instead of OFFSET, you filter on the last seen value. The query stays fast at any depth because it can use an index seek.
public interface PostRepository extends JpaRepository<Post, Long> {
@Query("SELECT p FROM Post p WHERE p.id < :lastId ORDER BY p.id DESC")
Slice<Post> findOlderThan(@Param("lastId") Long lastId, Pageable pageable);
}Streaming Large Result Sets
When you must process every row (export, batch job, report) but cannot hold them all in memory, return a Stream<T>. JPA reads rows lazily from a forward-only cursor.
Two rules are non-negotiable:
- The method must run inside a transaction (
@Transactional) so the cursor stays open. - You must close the stream — use try-with-resources — to release the DB cursor.
public interface OrderRepository extends JpaRepository<Order, Long> {
@QueryHints(@QueryHint(name = HINT_FETCH_SIZE, value = "100"))
@Query("SELECT o FROM Order o WHERE o.status = :status")
Stream<Order> streamByStatus(@Param("status") String status);
}Draining a Stream Safely
Wrap the stream in try-with-resources and keep the whole consumption inside one transactional method. Detach or clear entities periodically if you mutate them, so the persistence context does not grow unbounded.
@Service
public class OrderExporter {
private final OrderRepository repository;
public OrderExporter(OrderRepository repository) {
this.repository = repository;
}
@Transactional(readOnly = true)
public long exportPending(Consumer<Order> writer) {
long count = 0;
try (Stream<Order> stream = repository.streamByStatus("PENDING")) {
for (Order order : (Iterable<Order>) stream::iterator) {
writer.accept(order);
count++;
}
}
return count;
}
}Quick Check: Choosing the Right Return Type
You are building an infinite-scroll feed over a 5-million-row table. The UI never shows a total count, only a "load more" button. Which repository return type is the best fit?
Recap
You now have a toolbox for handling large result sets in Spring Data JPA:
- Page<T> — content plus total counts; pays for a
COUNT(*)query. Use when the UI shows total pages. - Slice<T> — content plus
hasNext(); no count query. Best for infinite scroll. - Sort — combine with
PageRequest.of(page, size, sort)for ordered, deterministic pages. - Keyset pagination — filter on the last seen value to keep deep pages fast.
- Stream<T> — lazy, forward-only processing inside
@Transactionalwith try-with-resources for huge batch jobs.
Match the return type to the access pattern, and your queries stay fast and memory-safe at any scale.
자주 묻는 질문
“페이지 매김, 정렬 및 슬라이스 스트리밍” 강의는 무료인가요?
네 — “페이지 매김, 정렬 및 슬라이스 스트리밍” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Spring Boot 4 Complete Guide 강의 전체를 잠금 해제할 수 있습니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.
“페이지 매김, 정렬 및 슬라이스 스트리밍”에서 뭘 배우나요?
대규모 데이터 집합과 무한 스크롤 UI를 위해 페이지 단위·정렬·스트리밍 결과 집합을 효율적으로 반환합니다. 브라우저에서 직접 실행하는 실습 코드로 Spring Boot 4 Complete Guide을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.
Spring Boot 4 Complete Guide을(를) 시작하는 데 경험이 필요한가요?
사전 경험은 필요하지 않습니다. CoddyKit의 Spring Boot 4 Complete Guide은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.
“페이지 매김, 정렬 및 슬라이스 스트리밍” 강의는 얼마나 걸리나요?
대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.
이 Spring Boot 4 Complete Guide 강의에서 코드를 작성하고 실행할 수 있나요?
네. 모든 Spring Boot 4 Complete Guide 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.
이 강의의 모든 강의
- 파생 쿼리 메서드와 키워드 해석
- @Query를 활용한 JPQL 및 네이티브 쿼리
- Specification과 기준 기반 동적 필터링
- 페이지 매김, 정렬 및 슬라이스 스트리밍