Dev/Backend
[BackEnd] 커뮤니티 게시물 목록 조회 API 쿼리를 QueryDsl로 구현해보기
sebinChu
2024. 10. 23. 00:57
개요
코알라 커뮤니티 기능 중 게시판 목록 조회 API를 위해 QueryDsl을 학습 및 구현해보았다.
요구사항은 다음과 같다.
- 정렬 (최신순, 조회수, 좋아요수)
- 검색 (제목, 내용, 작성자)
- 페이징
이 세 가지 조건 하에, DB에서 값을 가져오기 위해 QueryDsl을 활용하였다.
* JPQL 대신 QueryDsl을 사용한 이유
필요한 Entity, Request
게시판 전체 목록을 조회할 때 단순히 GET을 하는 것이 아니라, 개요에서 언급한 3가지 조건을 만족시켜야 한다.
조회 조건을 만족하기 위해 다음과 같은 Request record를 생성한다.
@Schema(description = "게시글 목록 검색 요청 객체")
public record SearchBoardRequest(
@Schema(description = "검색 키워드(게시글 제목, 게시글 내용, 작성자)")
String searchKeyword,
@Schema(description = "게시글 카테고리")
BoardCategory category,
@Schema(description = "게시글 정렬 조건", allowableValues = {"LATEST", "LIKE", "VIEW_COUNT"})
BoardSort sort,
@Min(1)
@Schema(description = "페이지 번호", type = "integer", requiredMode = RequiredMode.REQUIRED)
int page,
@Min(10)
@Schema(description = "페이지별 개수", type = "integer", requiredMode = RequiredMode.REQUIRED)
int size
) {
public PageRequest pageRequest() {
return PageRequest.of(page - 1, size);
}
}
- PageRequestJPA에서 제공하는 페이지 번호는 0부터 시작한다. 하지만 웹사이트 상에서의 페이지는 1부터 시작하므로 항상 -1을 설정한다.
- 이 Request 쿼리를 실행하면 결과로 Page<T> 객체가 반환되는데, 전체 데이터 수, 페이지 수, 현재 페이지의 데이터 목록 등을 포함하고 있다. 요청한 페이지 번호와 페이지 크기(한 페이지에 포함될 데이터 수)를 기반으로, DB 쿼리에서 데이터 범위를 지정할 수 있다.
- Request 객체를 record로 설정하는 이유
- 기본적으로 불변이라서 일관성 유지에 좋다.
- 생성자, toString() , hashCode(), equals() 메소드를 자동으로 생성한다.
- DTO(데이터 전송 객체)로 사용되어, 필드의 의미가 명확하다.
전체 코드를 나눠서 쿼리 살펴보기
1. select로 조건에 따른 결과를 Dto 형태로 반환
List<ListBoardDto> boardList =
queryFactory
.select(Projections.fields(
ListBoardDto.class,
board.id.as("boardId"),
board.category.stringValue().as("category"),
board.title,
board.member.name.as("createdName"),
board.createdTime,
Expressions.booleanTemplate(
"case when {0} > {1} then true else false end",
board.createdTime, LocalDateTime.now().minusDays(3))
.as("newBoardYn"),
board.viewCount,
board.fixYn,
board.deleteYn
)
)
- category는 다음과 같은 Enum 타입으로 정의되어 있다.
이런 enum 값을 문자열로 변환해야 DB에서 사용할 수 있다. 따라서 stringValue()로 변환해준다.
@Getter
@AllArgsConstructor
public enum BoardCategory {
NOTICE("공지"),
FREE("자유"),
QUESTION("질문"),
INFO("정보"),
PROMOTION("홍보");
private final String name;
}
- Expressions.booleanTemplate
최신글 여부를 위한 조건부로직(SQL의 CASE)이다. Template을 사용해서 복잡한 SQL 쿼리 로직을 동적으로 보다 간편하게 작성할 수 있다.
2. where 절로 카테고리, 키워드 설정
.where(
searchCategory(request.category()),
searchKeyword(request.searchKeyword()),
board.saveYn.isTrue()
)
- searchCategory 메소드에 따라 게시물의 카테고리랑 요청 카테고리랑 비교해서 일치하는 게시물만 반환한다.board 객체의 category를 QueryDsl의 eq 메소드로 일치하는지 비교한다.
private static Predicate searchCategory(BoardCategory category) {
if (category == null) {
return null;
}
return board.category.eq(category);
}
- searchKeyword도 마찬가지.
그런데 키워드는 세 가지 옵션이 가능하기에 다음과 같은 BooleanBuilder로 구현한다.
private Predicate searchKeyword(String searchKeyword) {
if (!StringUtils.hasText(searchKeyword)) {
return null;
}
BooleanBuilder builder = new BooleanBuilder();
builder.or(board.title.contains(searchKeyword));
builder.or(board.content.contains(searchKeyword));
builder.or(board.member.name.contains(searchKeyword));
return builder;
}
builder.or(...)를 사용하여 여러 조건을 결합하면, 주어진 키워드가 제목이나 내용, 작성자 이름 중 하나라도 포함될 경우 조건이 참이 된다.
- BooleanBuilder
조건을 or, and로 결합할 때 가독성이 좋아진다.
여러 조건을 동적으로 추가할 수 잇다.
프로젝트에서 처음 QueryDsl을 적용하며 Template이나 where절에 넣는 메소드 등 배운 점이 많아서 정리해보았다!