[백엔드] QueryDSL 페이징
2026.02.03. 14:12
1. 스프링(백엔드)
1.1 컨트롤러
@GetMapping("/{slug}")
public ResponseEntity<?> getPostList(
@PathVariable("slug") String slug
, PostSearchCondition param
, Pageable pageable
) {
Long boardId = boardService.getIdBySlug(slug);
Page<PostListItem> list = postService.getPostList(boardId, param, pageable);
return ResponseEntity.ok(list);
}1.2 서비스
@Override
public Page<PostListItem> getPostList(
Long boardId
, PostSearchCondition param
, Pageable pageable
){
return postRepo.findPosts(boardId, param, pageable);
}1.3 리포지토리
public interface PostRepository extends JpaRepository<Post, Long>, PostRepositoryCustom {} // QueryDSL을 사용할 때는 커스텀에 위치public interface PostRepositoryCustom { // 쿼리DSL 로직은 커스텀에 위치
Page<PostListItem> findPosts(Long boardId, PostSearchCondition param, Pageable pageable);
}1.4 구현체
@RequiredArgsConstructor
public class PostRepositoryCustomImpl implements PostRepositoryCustom{
//QueryDSL
private final JPAQueryFactory query;
@Override
public Page<PostListItem> findPosts(Long boardId, PostSearchCondition param, Pageable pageable) {
// 게시글 리스트 조회
List<PostListItem> list = query.select(new QPostListItem( //레코드와 순서 맞춰야함.
post.id,
post.boardId,
post.title,
post.writerId,
post.createdAt,
post.updatedAt,
post.viewcnt
)
)
.from(post)
.where(
post.boardId.eq(boardId)
.and(post.title.containsIgnoreCase(param.keyword())) // 검색 조건식
.and(post.deleted.isFalse()) //삭제 처리 제외
)
.orderBy(getOrderSpecifiers(pageable))
.offset(pageable.getOffset()) // skip first. pageNumber × pageSize
.limit(pageable.getPageSize()) // pagesize
.fetch(); // 쿼리 실행
// 게시글 개수 조회
Long total = query
.select(post.count())
.from(post)
.where(
post.boardId.eq(boardId)
.and(post.title.containsIgnoreCase(param.keyword())) // 검색 조건식
.and(post.deleted.isFalse()) //삭제 처리 제외
)
.fetchOne();
return new PageImpl<>(list, pageable, total == null ? 0 : total);
}
// sort 옵션 조회
private OrderSpecifier<?>[] getOrderSpecifiers(Pageable pageable){
OrderSpecifier<?>[] orders = pageable.getSort().stream()
.map(this::toOrderSpecifier)
.toArray(OrderSpecifier[]::new);
if(orders.length == 0){
return new OrderSpecifier[]{
post.createdAt.desc(),
post.id.desc()
};
}
return orders;
}
// sort 옵션 분류기
private OrderSpecifier<?> toOrderSpecifier(Sort.Order order){
boolean asc = order.isAscending();
switch (order.getProperty()){
case "createdAt":
return asc? post.createdAt.asc() : post.createdAt.desc();
default :
return post.createdAt.desc();
}
}
}2. 프론트
// 게시글 목록 조회
export const getPostList = async (
slug:string,
currentPage:number,
pageSize:number,
keyword:string
) => {
const res = await jsonApi.get(`/api/board/${slug}`, {
params : {
page:currentPage,
size: pageSize,
sort: 'createdAt,desc',
keyword: keyword
},
} );
return res.data;
}