[Project] QueryDSL

기 원·2025년 5월 1일

[Project] Spring-plus

목록 보기
3/8

Lv. 3 QueryDSL

조건

  • 검색 조건은 다음과 같아요.
    • 검색 키워드로 일정의 제목을 검색할 수 있어요.
      • 제목은 부분적으로 일치해도 검색이 가능해요.
    • 일정의 생성일 범위로 검색할 수 있어요.
      • 일정을 생성일 최신순으로 정렬해주세요.
    • 담당자의 닉네임으로도 검색이 가능해요.
      • 닉네임은 부분적으로 일치해도 검색이 가능해요.
  • 다음의 내용을 포함해서 검색 결과를 반환해주세요.
    • 일정에 대한 모든 정보가 아닌, 제목만 넣어주세요.
    • 해당 일정의 담당자 수를 넣어주세요.
    • 해당 일정의 총 댓글 개수를 넣어주세요.
  • 검색 결과는 페이징 처리되어 반환되도록 합니다.

1. 컨트롤러 작성

	// 키워드, 날짜, 닉네임으로 필터링된 할일 목록을 페이징 처리하여 조회
    @GetMapping("/todos/searchFilters")
    public ResponseEntity<Page<TodoSummaryResponse>> searchTodosWithFilters(
        @RequestParam(required = false) String keyword, // 제목 검색 키워드
        @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)LocalDate startDate, // 시작 날짜
        @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)LocalDate endDate, // 종료 날짜
        @RequestParam(required = false) String nickname, // 닉네임
        @PageableDefault(size = 10, sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable //페이징 정보
    ) {
        Page<TodoSummaryResponse> result = todoService.searchTodosWithFilters(keyword, nickname, startDate, endDate, pageable);
        return ResponseEntity.ok(result);
    }
  • required = false 사용하여 빈값도 받음

2. 응답 DTO 작성

public record TodoSummaryResponse (

	String title,
	Long managerCount,
	Long commentCount
) {
	// QueryDSL의 Projection 기반 생성자
	@QueryProjection
	public TodoSummaryResponse(String title, Long managerCount, Long commentCount) {
		this.title = title;
		this.managerCount = managerCount;
		this.commentCount = commentCount;
	}
}

3. Repository 작성

// 인터페이스
Page<TodoSummaryResponse> searchTodosWithFilters(String keyword, String nickname, LocalDate startDate, LocalDate endDate, Pageable pageable);

// 구현체
	@Override
	public Page<TodoSummaryResponse> searchTodosWithFilters(String keyword, String nickname, LocalDate startDate,
		LocalDate endDate, Pageable pageable) {
        // Q 클래스 선언
		QTodo todo = QTodo.todo;
		QUser user = QUser.user;
		QManager manager = QManager.manager;
		QComment comment = QComment.comment;

		// 실제 데이터 리스트 조회
		List<TodoSummaryResponse> content = queryFactory
			.select(Projections.constructor(
				TodoSummaryResponse.class, //DTO를 생성자 기반으로 맵핑
				todo.title,
				manager.countDistinct(), // 관리자 수
				comment.count() // 댓글 수
			))
			.from(todo)
			.leftJoin(todo.managers, manager)
			.leftJoin(manager.user, user)
			.leftJoin(todo.comments, comment)
			.where(
				titleContains(keyword), // 제목 필터
				nicknameContains(nickname), // 닉네임 필터
				createdBetween(startDate, endDate) // 날짜 필터
			)
			.groupBy(todo.id) // count를 쓰기위해 작성
			.orderBy(todo.createdAt.desc()) // 정렬 조건
			.offset(pageable.getOffset()) // 페이징 시작위치
			.limit(pageable.getPageSize()) // 페이징 한페이지 크기
			.fetch();

		// 전체갯수 조회 카운트용
		Long count = queryFactory
			.select(todo.countDistinct())
			.from(todo)
			.leftJoin(todo.managers, manager)
			.leftJoin(manager.user, user)
			.where(
				titleContains(keyword),
				nicknameContains(nickname),
				createdBetween(startDate, endDate)
			)
			.fetchOne();

		return new PageImpl<>(content, pageable, count);
	}

	private BooleanExpression titleContains(String keyword) {
		return StringUtils.hasText(keyword) ? QTodo.todo.title.contains(keyword) : null;
	}

	private BooleanExpression nicknameContains(String nickname) {
		return StringUtils.hasText(nickname) ? QManager.manager.user.nickname.contains(nickname) : null;
	}

	private BooleanExpression createdBetween(LocalDate start, LocalDate end) {
		if (start != null && end != null) {
			return QTodo.todo.createdAt.between(start.atStartOfDay(), end.plusDays(1).atStartOfDay());
		}
		return null;
	}

4. 서비스 작성

public Page<TodoSummaryResponse> searchTodosWithFilters(String keyword, String nickname, LocalDate startDate, LocalDate endDate, Pageable pageable) {
        return todoQueryRepository.searchTodosWithFilters(keyword, nickname, startDate, endDate, pageable);
    }
profile
노력하고 있다니까요?

0개의 댓글