▶ LEVEL 3
▷ 10. QueryDSL을 사용하여 검색 기능 만들기
● 문제의 요구사항
1. 새로운 API로 제작
2. 검색 조건 구성
- 검색 키워드로 일정의 제목을 검색할 수 있다.
(제목은 부분적으로 일치해도 검색이 가능)
- 일정의 생성일 범위로 검색
(일정을 생성일 최신순으로 정렬)
- 담당자의 닉네임으로도 검색 가능
(닉네임은 부분적으로 일치해도 검색 가능)
3. 검색 결과에 해당하는 내용이 들어있어야 한다.
- 일정에 대한 모든 정보가 아닌, 제목만 넣는다.
- 해당 일정의 담당자 수를 넣는다.
- 해당 일정의 총 댓글 개수를 넣는다.
4. 검색 결과는 페이징 처리되어 반환한다.
우선 글로 요구사항을 해결해보자
1. 새로운 API로 제작
검색 기능을 작동할 새로운 API를 Conrollter클래스에서 제작한다.
2. 검색 조건 구성
- 제목과, 닉네임이 부분적으로 일치했을 때도 검색하는 쿼리 조건을 추가한다.
- 일정의 생성일을 받아오고, 해당 범위 내에 있는 일정을 생성일을 기준으로 내림차순으로 정렬한다.
3. 검색 결과 출력
필요한 내용만 출력하도록 제작한다.
4. 검색 결과는 페이징 처리되어 반환
페이징 처리 기능을 추가하여 제작한다.
이제 제작을 해보도록 하자
우선 응답을 새로 만들어야하기 때문에 해당하는 DTO를 제작하도록 한다.
@Getter
@AllArgsConstructor
public class TodoSearchResponse {
private String title;
private Long managerCount;
private Long commentCount;
}
여기서 전에 생성자를 작성하는 것이 아니라 @AllArgsConstructor를 사용했는데
@AllArgsConstructor는 모든 필드를 포함한 생성자가 자동 생성되므로 따로 생성자를 만들 필요가 없기 때문에 사용하게 되었다.
해당 DTO의 경우에는 제목과 담당자 수, 댓글 수만 출력하게 된다.
응답용 DTO를 제작했으니 QueryDSL을 사용한 리포지토리를 작성해보도록 하자
public interface TodoRepositoryQuery {
Optional<Todo> findByIdWithUser(Long todoId);
Page<TodoSearchResponse> searchTodos(String keyword, LocalDateTime startDate, LocalDateTime endDate, String managerNickname, Pageable pageable);
}
QueryDSL을 사용할 것이기 때문에 별도로 생성한 TodoRepositoryQuery에 searchTodos 메서드를 작성한다.
@Override
public Page<TodoSearchResponse> searchTodos(String keyword, LocalDateTime startDate, LocalDateTime endDate, String managerNickname, Pageable pageable) {
QTodo todo = QTodo.todo;
QManager manager = QManager.manager;
QUser user = QUser.user;
QComment comment = QComment.comment;
JPQLQuery<TodoSearchResponse> query = queryFactory
.select(Projections.constructor(
TodoSearchResponse.class,
todo.title,
manager.countDistinct(),
comment.countDistinct()
))
.from(todo)
.leftJoin(todo.managers, manager)
.leftJoin(manager.user, user)
.leftJoin(todo.comments, comment)
.where(
titleContains(keyword),
createdAtBetween(startDate, endDate),
managerNicknameContains(managerNickname)
)
.groupBy(todo.id)
.orderBy(todo.createdAt.desc());
List<TodoSearchResponse> results = query
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
long total = query.fetchCount();
return new PageImpl<>(results, pageable, total);
}
private BooleanExpression titleContains(String keyword) {
return (keyword == null || keyword.isEmpty()) ? null : QTodo.todo.title.containsIgnoreCase(keyword);
}
private BooleanExpression createdAtBetween(LocalDateTime start, LocalDateTime end) {
return (start == null || end == null) ? null : QTodo.todo.createdAt.between(start, end);
}
private BooleanExpression managerNicknameContains(String managerNickname) {
return (managerNickname == null || managerNickname.isEmpty()) ? null : QUser.user.nickname.containsIgnoreCase(managerNickname);
}
그리고 QueryDSL로 작성한 쿼리문과 조건문들을 작성했다.
그 후에 Service와 Controller에 추가하여 마무리하였다.
@Transactional
public Page<TodoSearchResponse> searchTodos(String keyword, LocalDateTime startDate, LocalDateTime endDate, String managerNickname, Pageable pageable){
return todoRepository.searchTodos(keyword, startDate, endDate, managerNickname, pageable);
}
@GetMapping("/todos/search")
public ResponseEntity<Page<TodoSearchResponse>> searchTodos(
@RequestParam(required = false) String keyword,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime startDate,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime endDate,
@RequestParam(required = false) String managerNickname,
@PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
){
Page<TodoSearchResponse> result = todoService.searchTodos(keyword, startDate, endDate, managerNickname, pageable);
return ResponseEntity.ok(result);
}
컨트롤러의 경우 @RequestParam(required = false)를 통해서 해당 값을 받지 않아도 검색이 진행될 수 있게끔 작성하였다.
'TIL' 카테고리의 다른 글
| 플러스 주차 개인 과제 트러블 슈팅 TIL (0) | 2025.03.21 |
|---|---|
| 플러스 주차 개인 과제 TIL 7. (0) | 2025.03.18 |
| 플러스 주차 개인 과제 TIL 5. (0) | 2025.03.14 |
| 플러스 주차 개인과제 TIL 4. (0) | 2025.03.13 |
| 플러스 주차 개인과제 TIL 3. (0) | 2025.03.12 |