💡 .java를 클릭시 관련 커밋으로 이동💡
회고
구현하면서 매번 느끼지만 CRUD 중에서 R이 제일 어렵다고 느껴진다,,(나만그런가?)
현재까지 구현한 모든 Read 작업은 성능 최적화 없이 모든 데이터셋을 조회하고 있는데(프론트에서 필요한 데이터가 추가적으로 생기면 그때 가서 추가하는 것이 제거보다 힘들기 때문에 일단 모두 조회) 나중에 가서 코드 리팩토링 하고 쿼리 최적화와, 불필요 데이터들을 제거 등등을 해줘야겠다고 느낀다.
StudyMemberRepositoryCustom.java
// StudyInfoId와 orderByScore 를 통해 스터디의 모든 멤버들을 기여도별 or 가입순 정렬하여 조회한다.
public List<StudyMembersResponse> findStudyMembersByStudyInfoIdOrderByScore(Long studyInfoId, boolean orderByScore);
커스텀 레포지토리(Spring Data JPA와 같은 데이터 접근 프레임워크에서 제공하지 않는 특정한 쿼리나 로직을 구현하기 위해 사용자가 직접 정의함) 추가
StudyMemberRepositoryImpl.java
@Override
public List<StudyMembersResponse> findStudyMembersByStudyInfoIdOrderByScore(Long studyInfoId, boolean orderByScore) {
JPAQuery<StudyMembersResponse> query = queryFactory
.select(Projections.constructor(StudyMembersResponse.class,
studyMember.userId,
studyMember.role,
studyMember.status,
studyMember.score,
user.name,
user.profileImageUrl
))
.from(studyMember)
.join(user).on(user.id.eq(studyMember.userId))
.where(studyMember.studyInfoId.eq(studyInfoId)
.and(studyMember.status.eq(StudyMemberStatus.STUDY_ACTIVE)));
if (orderByScore) {
query = query.orderBy(studyMember.score.desc(), studyMember.userId.asc()); // 기여도별 내림차순, 동일 점수 시 사용자 ID 오름차순
} else {
query = query.orderBy(studyMember.userId.asc()); // 사용자 Id 오름차순 (가입순)
}
return query.fetch();
}
orderByScore가 true일때 기여도순
orderByScore가 false일때 가입순
(활동중인 멤버만 조회)
@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping("/member")
public class StudyMemberController {
private final StudyMemberService studyMemberService;
private final AuthService authService;
// 스터디에 속한 스터디원 조회 (기여도별 조회)
@ApiResponse(responseCode = "200", description = "스터디원 조회 성공", content = @Content(schema = @Schema(implementation = StudyMembersResponse.class)))
@GetMapping("/{studyInfoId}")
public JsonResult<?> readStudyMembers(@AuthenticationPrincipal User user,
@PathVariable(name = "studyInfoId") Long studyInfoId,
@RequestParam(name = "orderByScore", defaultValue = "false") boolean orderByScore) {
authService.findUserInfo(user);
return JsonResult.successOf(studyMemberService.readStudyMembers(studyInfoId, orderByScore));
}
@RequestParam(name = "orderByScore", defaultValue = "false") boolean orderByScore) 을 통해 기본값 orderByScore=false; → 가입순정렬
@Builder
@AllArgsConstructor
@Getter
public class StudyMembersResponse {
private Long userId; // 사용자 Id
private StudyMemberRole role; // 스터디 구성원 역할
private StudyMemberStatus status; // 스터디 구성원 상태
private int score; //기여도
private String name; // 이름
private String profileImageUrl; // 프로필 사진
}
멤버 조회 DTO 추가
// 스터디에 속한 스터디원 조회 (기여도별)
public List<StudyMembersResponse> readStudyMembers(Long studyInfoId, boolean orderByScore) {
// 스터디 조회 예외처리
studyInfoRepository.findById(studyInfoId).orElseThrow(() -> {
log.warn(">>>> {} : {} <<<<", studyInfoId, ExceptionMessage.STUDY_INFO_NOT_FOUND);
return new StudyInfoException(ExceptionMessage.STUDY_INFO_NOT_FOUND);
});
return studyMemberRepository.findStudyMembersByStudyInfoIdOrderByScore(studyInfoId, orderByScore);
}
멤버 조회 Service 구현
테스트
// 테스트용 스터디원 조회(플랫폼Id,이름,프로필사진)
public static User generatePlatfomIdAndNameAndProfile(String platformId, String name, String profileImageUrl) {
return User.builder()
.platformId(platformId)
.platformType(GOOGLE)
.role(USER)
.name(name)
.profileImageUrl(profileImageUrl)
.githubId("구글아이디")
.build();
}
테스트용 User Fixture 설정
// 테스트용 스코어 스터디원 생성 메서드
public static StudyMember createStudyMembersByScore(Long userId, Long studyInfoId, int score) {
return StudyMember.builder()
.userId(userId)
.studyInfoId(studyInfoId)
.score(score)
.role(StudyMemberRole.STUDY_MEMBER)
.status(StudyMemberStatus.STUDY_ACTIVE)
.build();
}
테스트용 Member Fixture 설정
StudyMemberControllerTest.java
@Test
public void 스터디원_기여도별_조회_테스트() throws Exception {
// given
User savedUser = userRepository.save(generateAuthUser());
Map<String, String> map = TokenUtil.createTokenMap(savedUser);
String accessToken = jwtService.generateAccessToken(map, savedUser);
String refreshToken = jwtService.generateRefreshToken(map, savedUser);
StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(savedUser.getId());
studyInfoRepository.save(studyInfo);
when(authService.authenticate(any(Long.class), any(User.class))).thenReturn(UserInfoResponse.builder().build());
when(studyMemberService.readStudyMembers(any(Long.class), any(boolean.class))).thenReturn(new ArrayList<>());
//when , then
mockMvc.perform(get("/member/{studyInfoId}", studyInfo.getId())
.contentType(MediaType.APPLICATION_JSON)
.header(AUTHORIZATION, createAuthorizationHeader(accessToken, refreshToken))
.param("userId", String.valueOf(savedUser.getId())))
// then
.andExpect(status().isOk())
.andExpect(jsonPath("$.res_code").value(200));
}
컨트롤러 동작 검증 ( HTTP 요청을 올바르게 처리하고 적절한 응답을 반환하는지 확인)
@Test
@DisplayName("스터디에 속한 스터디원 조회(기여도별) 테스트")
public void readStudyMembers_score() {
// given
boolean orderByScore = true;
User leader = UserFixture.generatePlatfomIdAndNameAndProfile("1", "이정우", "이정우프로필사진");
User activeMember1 = UserFixture.generatePlatfomIdAndNameAndProfile("2", "구영민", "구영민프로필사진");
User activeMember2 = UserFixture.generatePlatfomIdAndNameAndProfile("3", "이주성", "이주성프로필사진");
User activeMember3 = UserFixture.generatePlatfomIdAndNameAndProfile("4", "탁세하", "탁세하프로필사진");
userRepository.saveAll(List.of(leader, activeMember1, activeMember2, activeMember3));
StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(leader.getId());
studyInfoRepository.save(studyInfo);
studyMemberRepository.saveAll(List.of(
StudyMemberFixture.createStudyMembersByScore(leader.getId(), studyInfo.getId(), 77),
StudyMemberFixture.createStudyMembersByScore(activeMember1.getId(), studyInfo.getId(), 77),
StudyMemberFixture.createStudyMembersByScore(activeMember2.getId(), studyInfo.getId(), 99),
StudyMemberFixture.createStudyMembersByScore(activeMember3.getId(), studyInfo.getId(), 88)
));
// when
List<StudyMembersResponse> responses = studyMemberService.readStudyMembers(studyInfo.getId(), orderByScore);
// then
assertNotNull(responses);
assertEquals(4, responses.size());
assertEquals(99, responses.get(0).getScore());
assertEquals("이주성", responses.get(0).getName());
assertEquals(88, responses.get(1).getScore());
assertEquals("탁세하", responses.get(1).getName());
assertEquals(77, responses.get(2).getScore());
assertEquals("이정우", responses.get(2).getName());
assertEquals(77, responses.get(3).getScore());
assertEquals("구영민", responses.get(3).getName()); // 점수 동일시 userId 낮은순
}
@Test
@DisplayName("스터디에 속한 스터디원 조회(가입순) 테스트")
public void readStudyMembers_userId() {
// given
boolean orderByScore = false;
User leader = UserFixture.generatePlatfomIdAndNameAndProfile("1", "이정우", "이정우프로필사진");
User activeMember1 = UserFixture.generatePlatfomIdAndNameAndProfile("2", "구영민", "구영민프로필사진");
User activeMember2 = UserFixture.generatePlatfomIdAndNameAndProfile("3", "이주성", "이주성프로필사진");
User activeMember3 = UserFixture.generatePlatfomIdAndNameAndProfile("4", "탁세하", "탁세하프로필사진");
userRepository.saveAll(List.of(leader, activeMember1, activeMember2, activeMember3));
StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(leader.getId());
studyInfoRepository.save(studyInfo);
studyMemberRepository.saveAll(List.of(
StudyMemberFixture.createStudyMembersByScore(leader.getId(), studyInfo.getId(), 77),
StudyMemberFixture.createStudyMembersByScore(activeMember1.getId(), studyInfo.getId(), 77),
StudyMemberFixture.createStudyMembersByScore(activeMember2.getId(), studyInfo.getId(), 99),
StudyMemberFixture.createStudyMembersByScore(activeMember3.getId(), studyInfo.getId(), 88)
));
// when
List<StudyMembersResponse> responses = studyMemberService.readStudyMembers(studyInfo.getId(), orderByScore);
// then
assertNotNull(responses);
assertEquals(4, responses.size());
assertEquals(77, responses.get(0).getScore());
assertEquals("이정우", responses.get(0).getName());
assertEquals(77, responses.get(1).getScore());
assertEquals("구영민", responses.get(1).getName());
assertEquals(99, responses.get(2).getScore());
assertEquals("이주성", responses.get(2).getName());
assertEquals(88, responses.get(3).getScore());
assertEquals("탁세하", responses.get(3).getName());
}
@Test
@DisplayName("스터디에 속한 스터디원 조회 - 활동중이지 않은 멤버가 있는 경우 테스트")
public void readStudyWithdrawalMembers_userId() {
// given
boolean orderByScore = true;
User leader = UserFixture.generatePlatfomIdAndNameAndProfile("1", "이정우", "이정우프로필사진");
User activeMember1 = UserFixture.generatePlatfomIdAndNameAndProfile("2", "구영민", "구영민프로필사진");
User activeMember2 = UserFixture.generatePlatfomIdAndNameAndProfile("3", "이주성", "이주성프로필사진");
User activeMember3 = UserFixture.generatePlatfomIdAndNameAndProfile("4", "탁세하", "탁세하프로필사진");
userRepository.saveAll(List.of(leader, activeMember1, activeMember2, activeMember3));
StudyInfo studyInfo = StudyInfoFixture.createDefaultPublicStudyInfo(leader.getId());
studyInfoRepository.save(studyInfo);
// 영민,세하 활동중이지 않은 멤버 지정
studyMemberRepository.saveAll(List.of(
StudyMemberFixture.createStudyActiveMembersByScore(leader.getId(), studyInfo.getId(), 77),
StudyMemberFixture.createStudyWithdrawalMembersByScore(activeMember1.getId(), studyInfo.getId(), 77),
StudyMemberFixture.createStudyActiveMembersByScore(activeMember2.getId(), studyInfo.getId(), 99),
StudyMemberFixture.createStudyWithdrawalMembersByScore(activeMember3.getId(), studyInfo.getId(), 88)
));
// when
List<StudyMembersResponse> responses = studyMemberService.readStudyMembers(studyInfo.getId(), orderByScore);
// then
assertNotNull(responses);
assertEquals(2, responses.size());
assertEquals(99, responses.get(0).getScore());
assertEquals("이주성", responses.get(0).getName());
assertEquals(77, responses.get(1).getScore());
assertEquals("이정우", responses.get(1).getName());
}
스터디원 조회 기여도별, 가입순별 테스트, 활동중이지 않은 멤버가 있는 경우 테스트
'Project > 깃터디 (gitudy)' 카테고리의 다른 글
[깃터디/Convention] 컨벤션 조회 api 구현 (1) | 2024.05.11 |
---|---|
[깃터디/Convention] 컨벤션 등록 api 구현 (0) | 2024.05.11 |
[깃터디/Member] 스터디 가입 신청 목록 조회 api 구현 (0) | 2024.05.11 |
[깃터디/Member] 스터디원 강퇴, 스터디원 탈퇴 api 구현 (0) | 2024.05.11 |
[깃터디/Member] 스터디장 가입 신청 승인/거부 api 구현 (0) | 2024.05.11 |
댓글