Skip to content

페이지네이션 가이드

목록 조회(findByPage류 API)는 필터 조건과 페이지 정보를 하나의 요청/응답 봉투(PaginationRequestDto/ PaginationResponseDto)에 함께 담는다. dictionary-api의 board 기능(POST /v1/board/find-by-page)을 예시로 요청부터 응답까지 전체 흐름을 정리한다.

  1. Pagination — 요청 시엔 pageNumber/pageSize만, 응답 시엔 totalCount까지 채워진다.
    common/dto/pagination/Pagination.java
    @Getter
    @Setter
    @Builder
    public final class Pagination {
    @Min(0)
    private int pageNumber;
    @Min(1)
    private int pageSize;
    @JsonInclude(JsonInclude.Include.NON_NULL)
    private long totalCount; // 응답에만 사용
    public Pageable toPageable() {
    return PageRequest.of(pageNumber, pageSize);
    }
    public static Pagination toPagination(final Page<?> page) {
    return Pagination.builder()
    .pageNumber(page.getNumber())
    .pageSize(page.getSize())
    .totalCount(page.getTotalElements())
    .build();
    }
    }
  2. 요청 — PaginationRequestDto<T>가 필터 조건(payload)과 pagination을 함께 싣는다.
    common/dto/pagination/PaginationRequestDto.java
    public class PaginationRequestDto<T> extends RequestDto<T> {
    @NotNull
    @Valid
    private Pagination pagination = null;
    }
    POST /v1/board/find-by-page 요청
    {
    "payload": {
    "title": "금상"
    },
    "pagination": {
    "pageNumber": 0,
    "pageSize": 10
    }
    }
  3. 컨트롤러 → 서비스 → QueryDsl 리포지토리
    api/board/controller/BoardController.java
    @PostMapping(value = "/find-by-page", consumes = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<PaginationResponseDto<BoardResponseDto>> findByPage(
    @Valid @RequestBody final PaginationRequestDto<BoardFindByPageRequestDto> request) {
    return ok(new PaginationResponseDto<>(boardService.findByPage(request).map(BoardResponseDto::from)));
    }
    api/board/repository/BoardQueryDslRepositoryImpl.java
    @Override
    public Page<BoardEntity> findByPage(final PaginationRequestDto<BoardFindByPageRequestDto> request) {
    final QBoardEntity board = QBoardEntity.boardEntity;
    final BooleanBuilder where = buildSearchCondition(board, request.getPayload());
    final Pageable pageable = request.getPagination().toPageable();
    final List<BoardEntity> content =
    queryFactory
    .selectFrom(board)
    .where(where)
    .orderBy(board.createDt.desc(), board.id.desc())
    .offset(pageable.getOffset())
    .limit(pageable.getPageSize())
    .fetch();
    final Long total = queryFactory.select(board.id.count()).from(board).where(where).fetchOne();
    return new PageImpl<>(content, pageable, total == null ? 0 : total);
    }
    조회용 쿼리(offset/limit)와 전체 건수용 쿼리(count)를 따로 날려 PageImpl로 묶는다 — 동일한 where 조건을 두 쿼리에 그대로 재사용한다.
  4. 응답 — PaginationResponseDto<T>가 Page<T>를 그대로 받아 payload(content)와 pagination (totalCount 포함)을 함께 채운다.
    common/dto/pagination/PaginationResponseDto.java
    public class PaginationResponseDto<T> extends ResponseDto<List<T>> {
    private Pagination pagination;
    public PaginationResponseDto(final Page<T> payload) {
    super(payload.getContent());
    this.pagination = Pagination.toPagination(payload);
    }
    }
    응답
    {
    "successYn": "Y",
    "messageCd": "SUCCESS",
    "payload": [
    { "id": 1000, "title": "[금상] 눈보라 휘날리며(유영복作)", "createDt": "2026-08-21T10:00:00" }
    ],
    "pagination": {
    "pageNumber": 0,
    "pageSize": 10,
    "totalCount": 1
    }
    }