페이지네이션 가이드
목록 조회(findByPage류 API)는 필터 조건과 페이지 정보를 하나의 요청/응답 봉투(PaginationRequestDto/
PaginationResponseDto)에 함께 담는다. dictionary-api의 board 기능(POST /v1/board/find-by-page)을
예시로 요청부터 응답까지 전체 흐름을 정리한다.
Pagination— 요청 시엔pageNumber/pageSize만, 응답 시엔totalCount까지 채워진다.common/dto/pagination/Pagination.java @Getter@Setter@Builderpublic 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();}}- 요청 —
PaginationRequestDto<T>가 필터 조건(payload)과pagination을 함께 싣는다.common/dto/pagination/PaginationRequestDto.java public class PaginationRequestDto<T> extends RequestDto<T> {@NotNull@Validprivate Pagination pagination = null;}POST /v1/board/find-by-page 요청 {"payload": {"title": "금상"},"pagination": {"pageNumber": 0,"pageSize": 10}} - 컨트롤러 → 서비스 → 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 @Overridepublic 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조건을 두 쿼리에 그대로 재사용한다. - 응답 —
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}}