Skip to content

Commit

Permalink
refactor: findApprovedApplicants의 N+1 쿼리를 개선한다. (#447)
Browse files Browse the repository at this point in the history
* refactor: findApprovedApplicants를 fetch join에서 dto projections으로 변경한다.

* chore: 보호소, 봉사자 프론트 url을 변경한다.
  • Loading branch information
bjo6300 authored Jan 3, 2024
1 parent f49064d commit 4a18216
Show file tree
Hide file tree
Showing 11 changed files with 151 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@
import java.util.List;

public record FindApprovedApplicantsResponse(
List<FindApplicant> applicants
List<FindApprovedApplicantResponse> applicants
) {

private record FindApplicant(
public record FindApprovedApplicantResponse(
Long volunteerId,
Long applicantId,
String volunteerName,
Expand All @@ -19,8 +19,8 @@ private record FindApplicant(
boolean volunteerAttendance
) {

private static FindApplicant from(Applicant applicant) {
return new FindApplicant(
public static FindApprovedApplicantResponse from(Applicant applicant) {
return new FindApprovedApplicantResponse(
applicant.getVolunteer().getVolunteerId(),
applicant.getApplicantId(),
applicant.getVolunteer().getName(),
Expand All @@ -31,13 +31,4 @@ private static FindApplicant from(Applicant applicant) {
);
}
}

public static FindApprovedApplicantsResponse from(List<Applicant> applicants) {
return new FindApprovedApplicantsResponse(
applicants.stream()
.map(FindApplicant::from)
.toList()
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.clova.anifriends.domain.applicant.Applicant;
import com.clova.anifriends.domain.applicant.repository.response.FindApplicantResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApplyingVolunteerResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApprovedApplicantsResult;
import com.clova.anifriends.domain.applicant.vo.ApplicantStatus;
import com.clova.anifriends.domain.recruitment.Recruitment;
import com.clova.anifriends.domain.shelter.Shelter;
Expand Down Expand Up @@ -44,15 +45,26 @@ Optional<Applicant> findByApplicantIdAndVolunteerId(
@Param("volunteerId") Long volunteerId
);

@Query("select a from Applicant a "
+ "join fetch a.recruitment r "
+ "join fetch r.shelter s "
+ "join fetch a.volunteer v "
+ "where r.recruitmentId = :recruitmentId "
+ "and s.shelterId = :shelterId "
+ "and (a.status = com.clova.anifriends.domain.applicant.vo.ApplicantStatus.ATTENDANCE "
+ "or a.status = com.clova.anifriends.domain.applicant.vo.ApplicantStatus.NOSHOW)")
List<Applicant> findApprovedApplicants(
@Query(
"""
select v.volunteerId as volunteerId,
a.applicantId as applicantId,
v.name.name as volunteerName,
v.birthDate as volunteerBirthDate,
v.gender as volunteerGender,
v.phoneNumber.phoneNumber as volunteerPhoneNumber,
a.status as applicantStatus
from Applicant a
join a.volunteer v
join a.recruitment.shelter s
join a.recruitment r
where r.recruitmentId = :recruitmentId
and s.shelterId = :shelterId
and a.status = com.clova.anifriends.domain.applicant.vo.ApplicantStatus.ATTENDANCE
or a.status = com.clova.anifriends.domain.applicant.vo.ApplicantStatus.NOSHOW
"""
)
List<FindApprovedApplicantsResult> findApprovedApplicants(
@Param("recruitmentId") Long recruitmentId,
@Param("shelterId") Long shelterId
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.clova.anifriends.domain.applicant.repository.response;

import com.clova.anifriends.domain.applicant.vo.ApplicantStatus;
import com.clova.anifriends.domain.volunteer.vo.VolunteerGender;
import java.time.LocalDate;

public interface FindApprovedApplicantsResult {

Long getVolunteerId();

Long getApplicantId();

String getVolunteerName();

LocalDate getVolunteerBirthDate();

VolunteerGender getVolunteerGender();

String getVolunteerPhoneNumber();

ApplicantStatus getApplicantStatus();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@
import com.clova.anifriends.domain.applicant.dto.FindApplicantsResponse.FindApplicantResponse;
import com.clova.anifriends.domain.applicant.dto.response.FindApplyingVolunteersResponse;
import com.clova.anifriends.domain.applicant.dto.response.FindApplyingVolunteersResponse.FindApplyingVolunteerResponse;
import com.clova.anifriends.domain.applicant.dto.response.FindApprovedApplicantsResponse;
import com.clova.anifriends.domain.applicant.dto.response.FindApprovedApplicantsResponse.FindApprovedApplicantResponse;
import com.clova.anifriends.domain.applicant.repository.response.FindApplicantResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApplyingVolunteerResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApprovedApplicantsResult;
import com.clova.anifriends.domain.applicant.vo.ApplicantStatus;
import com.clova.anifriends.domain.common.PageInfo;
import com.clova.anifriends.domain.recruitment.Recruitment;
import java.util.List;
Expand Down Expand Up @@ -50,4 +54,23 @@ public static FindApplicantsResponse resultToResponse(
.toList();
return new FindApplicantsResponse(responses, recruitment.getCapacity());
}

public static FindApprovedApplicantsResponse resultToResponse(
List<FindApprovedApplicantsResult> applicantsApproved
) {
List<FindApprovedApplicantResponse> response = applicantsApproved.stream()
.map(findApprovedApplicantsResult -> new FindApprovedApplicantResponse(
findApprovedApplicantsResult.getVolunteerId(),
findApprovedApplicantsResult.getApplicantId(),
findApprovedApplicantsResult.getVolunteerName(),
findApprovedApplicantsResult.getVolunteerBirthDate(),
findApprovedApplicantsResult.getVolunteerGender(),
findApprovedApplicantsResult.getVolunteerPhoneNumber(),
findApprovedApplicantsResult.getApplicantStatus().equals(ApplicantStatus.ATTENDANCE)
)
)
.toList();

return new FindApprovedApplicantsResponse(response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import com.clova.anifriends.domain.applicant.repository.ApplicantRepository;
import com.clova.anifriends.domain.applicant.repository.response.FindApplicantResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApplyingVolunteerResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApprovedApplicantsResult;
import com.clova.anifriends.domain.applicant.service.dto.UpdateApplicantAttendanceCommand;
import com.clova.anifriends.domain.applicant.vo.ApplicantStatus;
import com.clova.anifriends.domain.notification.ShelterNotification;
Expand All @@ -16,9 +17,9 @@
import com.clova.anifriends.domain.notification.repository.VolunteerNotificationRepository;
import com.clova.anifriends.domain.notification.vo.NotificationType;
import com.clova.anifriends.domain.recruitment.Recruitment;
import com.clova.anifriends.domain.recruitment.dto.response.IsAppliedRecruitmentResponse;
import com.clova.anifriends.domain.recruitment.exception.RecruitmentNotFoundException;
import com.clova.anifriends.domain.recruitment.repository.RecruitmentRepository;
import com.clova.anifriends.domain.recruitment.dto.response.IsAppliedRecruitmentResponse;
import com.clova.anifriends.domain.review.exception.ApplicantNotFoundException;
import com.clova.anifriends.domain.shelter.Shelter;
import com.clova.anifriends.domain.shelter.exception.ShelterNotFoundException;
Expand Down Expand Up @@ -78,9 +79,9 @@ public FindApplyingVolunteersResponse findApplyingVolunteers(
@Transactional(readOnly = true)
public FindApprovedApplicantsResponse findApprovedApplicants(Long shelterId,
Long recruitmentId) {
List<Applicant> applicantsApproved = applicantRepository
List<FindApprovedApplicantsResult> applicantsApproved = applicantRepository
.findApprovedApplicants(recruitmentId, shelterId);
return FindApprovedApplicantsResponse.from(applicantsApproved);
return ApplicantMapper.resultToResponse(applicantsApproved);
}

@Transactional(readOnly = true)
Expand Down
2 changes: 1 addition & 1 deletion src/main/resources/backend-config
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ void newApplicant() {

//then
assertThat(applicant.getRecruitment()).isEqualTo(recruitment);
assertThat(applicant.isAttendance()).isEqualTo(false);
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import com.clova.anifriends.domain.applicant.dto.response.FindApplyingVolunteersResponse;
import com.clova.anifriends.domain.applicant.dto.response.FindApplyingVolunteersResponse.FindApplyingVolunteerResponse;
import com.clova.anifriends.domain.applicant.dto.response.FindApprovedApplicantsResponse;
import com.clova.anifriends.domain.applicant.dto.response.FindApprovedApplicantsResponse.FindApprovedApplicantResponse;
import com.clova.anifriends.domain.applicant.support.ApplicantFixture;
import com.clova.anifriends.domain.common.PageInfo;
import com.clova.anifriends.domain.recruitment.Recruitment;
Expand Down Expand Up @@ -149,10 +150,18 @@ void findApplyingVolunteers() throws Exception {
@DisplayName("봉사 신청 승인자 조회 API 호출 시")
void findApprovedApplicants() throws Exception {
// given
FindApprovedApplicantsResponse.from(List.of());
FindApprovedApplicantResponse findApprovedApplicantResponse = new FindApprovedApplicantResponse(
1L,
2L,
"김이름",
LocalDate.now(),
VolunteerGender.MALE,
"010-2382-1832",
true
);

when(applicantService.findApprovedApplicants(anyLong(), anyLong()))
.thenReturn(FindApprovedApplicantsResponse.from(List.of()));
.thenReturn(new FindApprovedApplicantsResponse(List.of(findApprovedApplicantResponse)));

// when
ResultActions result = mockMvc.perform(
Expand All @@ -174,7 +183,8 @@ void findApprovedApplicants() throws Exception {
fieldWithPath("applicants[]").type(ARRAY).description("봉사 신청자 리스트").optional(),
fieldWithPath("applicants[].applicantId").type(NUMBER).description("봉사 신청 ID"),
fieldWithPath("applicants[].volunteerId").type(NUMBER).description("봉사자 ID"),
fieldWithPath("applicants[].volunteerBirthdate").type(STRING)
fieldWithPath("applicants[].volunteerName").type(STRING).description("봉사자 이름"),
fieldWithPath("applicants[].volunteerBirthDate").type(STRING)
.description("봉사자 생일"),
fieldWithPath("applicants[].volunteerGender").type(STRING)
.description("봉사자 성별"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import com.clova.anifriends.domain.applicant.Applicant;
import com.clova.anifriends.domain.applicant.repository.response.FindApplicantResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApplyingVolunteerResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApprovedApplicantsResult;
import com.clova.anifriends.domain.applicant.support.ApplicantFixture;
import com.clova.anifriends.domain.recruitment.Recruitment;
import com.clova.anifriends.domain.recruitment.support.fixture.RecruitmentFixture;
Expand Down Expand Up @@ -70,12 +71,12 @@ void findApprovedByRecruitmentIdAndShelterId1() {
List<Applicant> expected = List.of(applicantAttendance, applicantNoShow);

// when
List<Applicant> result = applicantRepository
List<FindApprovedApplicantsResult> result = applicantRepository
.findApprovedApplicants(recruitment.getRecruitmentId(),
shelter.getShelterId());

// then
assertThat(result).isEqualTo(expected);
assertThat(result.size()).isEqualTo(expected.size());
}

@Test
Expand All @@ -97,15 +98,16 @@ void findApprovedByRecruitmentIdAndShelterId2() {
applicantRepository.saveAll(
List.of(applicantPending, applicantRefused)
);

List<Applicant> expected = List.of();

// when
List<Applicant> result = applicantRepository
List<FindApprovedApplicantsResult> result = applicantRepository
.findApprovedApplicants(recruitment.getRecruitmentId(),
shelter.getShelterId());

// then
assertThat(result).isEqualTo(expected);
assertThat(result.size()).isEqualTo(expected.size());
}
}

Expand Down Expand Up @@ -197,7 +199,7 @@ void findApplyingVolunteersWhen3Recruitment2Review() {
.containsExactly(
applicantShouldWriteReview1.getApplicantId(),
applicantShouldWriteReview2.getApplicantId()
);
);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.clova.anifriends.domain.applicant.service;

import static com.clova.anifriends.domain.applicant.support.ApplicantFixture.applicant;
import static com.clova.anifriends.domain.applicant.vo.ApplicantStatus.ATTENDANCE;
import static com.clova.anifriends.domain.applicant.vo.ApplicantStatus.NOSHOW;
import static com.clova.anifriends.domain.applicant.vo.ApplicantStatus.PENDING;
Expand All @@ -27,15 +26,17 @@
import com.clova.anifriends.domain.applicant.repository.ApplicantRepository;
import com.clova.anifriends.domain.applicant.repository.response.FindApplicantResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApplyingVolunteerResult;
import com.clova.anifriends.domain.applicant.repository.response.FindApprovedApplicantsResult;
import com.clova.anifriends.domain.applicant.service.dto.UpdateApplicantAttendanceCommand;
import com.clova.anifriends.domain.applicant.support.ApplicantDtoFixture;
import com.clova.anifriends.domain.applicant.support.ApplicantFixture;
import com.clova.anifriends.domain.applicant.vo.ApplicantStatus;
import com.clova.anifriends.domain.notification.repository.ShelterNotificationRepository;
import com.clova.anifriends.domain.notification.repository.VolunteerNotificationRepository;
import com.clova.anifriends.domain.recruitment.Recruitment;
import com.clova.anifriends.domain.recruitment.dto.response.IsAppliedRecruitmentResponse;
import com.clova.anifriends.domain.recruitment.exception.RecruitmentNotFoundException;
import com.clova.anifriends.domain.recruitment.repository.RecruitmentRepository;
import com.clova.anifriends.domain.recruitment.dto.response.IsAppliedRecruitmentResponse;
import com.clova.anifriends.domain.recruitment.support.fixture.RecruitmentFixture;
import com.clova.anifriends.domain.recruitment.vo.RecruitmentApplicantCount;
import com.clova.anifriends.domain.recruitment.vo.RecruitmentInfo;
Expand Down Expand Up @@ -103,7 +104,6 @@ class RegisterApplicantTest {
30
);


@Test
@DisplayName("성공")
void registerApplicant() {
Expand Down Expand Up @@ -181,13 +181,15 @@ void findApplicantsApproved1() {
// given
Recruitment recruitment = recruitment(shelter());
Volunteer volunteer = volunteer();
Applicant applicantApproved = applicant(recruitment, volunteer, ATTENDANCE);

FindApprovedApplicantsResponse response = FindApprovedApplicantsResponse.from(
List.of(applicantApproved));
Applicant applicant = ApplicantFixture.applicant(recruitment, volunteer,
ApplicantStatus.APPROVED);
FindApprovedApplicantsResult findApprovedApplicantsResult = ApplicantDtoFixture.findApprovedApplicantsResult(
applicant.getStatus());
FindApprovedApplicantsResponse response = ApplicantMapper.resultToResponse(
List.of(findApprovedApplicantsResult));

when(applicantRepository.findApprovedApplicants(anyLong(), anyLong()))
.thenReturn(List.of(applicantApproved));
.thenReturn(List.of(findApprovedApplicantsResult));

// when
FindApprovedApplicantsResponse result = applicantService.findApprovedApplicants(
Expand All @@ -201,9 +203,6 @@ void findApplicantsApproved1() {
@DisplayName("성공: 봉사 승인자가 0 명인 경우")
void findApplicantsApproved2() {
// given
FindApprovedApplicantsResponse response = FindApprovedApplicantsResponse.from(
List.of());

when(applicantRepository.findApprovedApplicants(anyLong(), anyLong()))
.thenReturn(List.of());

Expand All @@ -212,7 +211,7 @@ void findApplicantsApproved2() {
anyLong(), anyLong());

// then
assertThat(result).isEqualTo(response);
assertThat(result.applicants().size()).isZero();
}
}

Expand Down
Loading

0 comments on commit 4a18216

Please sign in to comment.