▶ 오늘 한일
- 오늘은 차주 목요일에 발재된 Spring 심화 주차 개인 과제 필수 단계를 완성했다.
▶ 진행 사항
- LV 1 ~ LV 3 까지의 필수 단계
▷ LV 1. 코드 개선
● 코드 개선 퀴즈 - Early Return
■ 수정 (검사 우선 순위 변경)
@Transactional
public SignupResponse signup(SignupRequest signupRequest) {
if (userRepository.existsByEmail(signupRequest.getEmail())) { // 중복여부 선 검사 진행
throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}
String encodedPassword = passwordEncoder.encode(signupRequest.getPassword()); // 비밀번호 암호화
UserRole userRole = UserRole.of(signupRequest.getUserRole()); // 사용자 역할 지정
// if (userRepository.existsByEmail(signupRequest.getEmail())) { // 중복여부 검사
// throw new InvalidRequestException("이미 존재하는 이메일입니다.");
// }
● 리팩토링 퀴즈 - 불필요한 if-else 피하기
■ 수정 (if 이후 return 또는 실행할 코드가 있을경우 else 불필요)
@Component
public class WeatherClient {
private final RestTemplate restTemplate;
public WeatherClient(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
public String getTodayWeather() {
ResponseEntity<WeatherDto[]> responseEntity =
restTemplate.getForEntity(buildWeatherApiUri(), WeatherDto[].class);
WeatherDto[] weatherArray = responseEntity.getBody();
if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) { // 단순히 else 부분 제거
throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode()); // if 바로뒤에 실행할 코드가 있으면 else 가 필요 없음
}
if (weatherArray == null || weatherArray.length == 0) { // if(검사)를 개별적으로 수행하여 가독성 향상
throw new ServerException("날씨 데이터가 없습니다.");
}
● 코드 개선 퀴즈 - Validation
■ 수정
@Transactional
public void changePassword(long userId, UserChangePasswordRequest userChangePasswordRequest) {
// if (userChangePasswordRequest.getNewPassword().length() < 8 ||
// !userChangePasswordRequest.getNewPassword().matches(".*\\d.*") ||
// !userChangePasswordRequest.getNewPassword().matches(".*[A-Z].*")) {
// throw new InvalidRequestException("새 비밀번호는 8자 이상이어야 하고, 숫자와 대문자를 포함해야 합니다.");
// } // DTO 에서 처리하도록 수정
↓
@Getter
@NoArgsConstructor
@AllArgsConstructor
public class UserChangePasswordRequest {
@NotBlank(message = "새로운 비밀번호를 입력해주세요.")
@Size(min = 8, message = "새 비밀번호는 최소 8자 이상이어야 합니다.") // 비밀번호 길이 체크
@Pattern(regexp = ".*\\d.*", message = "비밀번호는 숫자를 포함해야 합니다.")
@Pattern(regexp = ".*[A-Z]*", message = "비밀번호는 대문자를 포함해야 합니다.") // 숫자와 대문자 포함
private String oldPassword;
@NotBlank(message = "기존 비밀번호를 입력해주세요.")
private String newPassword;
}
▷ LV 2. N+1 문제
■ 수정 (@EntityGraph 는 Fetch Join 의 간편 버전인것을 배움)
public interface TodoRepository extends JpaRepository<Todo, Long> {
// @EntityGraph 는 FETCH JOIN 의 간편 버전
// @Query("SELECT t FROM Todo t LEFT JOIN FETCH t.user u ORDER BY t.modifiedAt DESC")
@EntityGraph(attributePaths = {"user"})
Page<Todo> findAllByOrderByModifiedAtDesc(Pageable pageable);
// @Query("SELECT t FROM Todo t " +
// "LEFT JOIN FETCH t.user " +
// "WHERE t.id = :todoId")
@EntityGraph(attributePaths = {"user"}) // 기존 페치 조인을 위한 JPQL 퀴리를 짜지 않고, @EntityGraph 를 활용하여 페치 조인을 진행
Optional<Todo> findById(@Param("todoId") Long todoId);
int countById(Long todoId);
}
▷ LV 3. 테스트 코드 연습
● 예상대로 성공하는지에 대한 케이스
■ 수정 (rawPassword 와 encodedPassword 의 인자 순서 변경)
@ExtendWith(SpringExtension.class)
class PasswordEncoderTest {
@InjectMocks // 테스트 대상 객체
private PasswordEncoder passwordEncoder;
@Test
void matches_메서드가_정상적으로_동작한다() {
// given
String rawPassword = "testPassword";
String encodedPassword = passwordEncoder.encode(rawPassword);
// when
boolean matches = passwordEncoder.matches(rawPassword, encodedPassword); // 인자 순서 변경
// 원본 비밀번호를 암호화한 후 기존 암호화 된 값을 비교하기 때문
// then
assertTrue(matches);
}
}
● 예상대로 예외처리 하는지에 대한 케이스
└ Case 1
■ 수정 (코드와 메서드명 변경)
@Test
public void manager_목록_조회_시_Todo가_없다면_InvalidRequestException_을_던진다() { // 메서드명 변경
// given
long todoId = 1L;
given(todoRepository.findById(todoId)).willReturn(Optional.empty());
// when & then
InvalidRequestException exception = assertThrows(InvalidRequestException.class,
() -> managerService.getManagers(todoId),
"Manager not found"
);
}
└ Case 2
■ 수정 (CommentService 에 saveComment 가 InvalidRequestException으로 던지므로 맞게 수정)
@Test
public void comment_등록_중_할일을_찾지_못해_에러가_발생한다() {
// given
long todoId = 1;
CommentSaveRequest request = new CommentSaveRequest("contents");
AuthUser authUser = new AuthUser(1L, "email", UserRole.USER);
given(todoRepository.findById(anyLong())).willReturn(Optional.empty());
// when
Exception exception = assertThrows(InvalidRequestException.class, () -> {
commentService.saveComment(authUser, todoId, request);
});
// then
assertEquals("Todo not found", exception.getMessage());
└ Case 3
■ 수정 (getUser 가 null 일 경우 추가)
@Transactional
public ManagerSaveResponse saveManager(AuthUser authUser, long todoId, ManagerSaveRequest managerSaveRequest) {
// 일정을 만든 유저
User user = User.fromAuthUser(authUser);
Todo todo = todoRepository.findById(todoId)
.orElseThrow(() -> new InvalidRequestException("Todo not found"));
if (todo.getUser() == null) { // getUser 가 null 일 경우 추가
throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.");
}
if (!ObjectUtils.nullSafeEquals(user.getId(), todo.getUser().getId())) {
throw new InvalidRequestException("담당자를 등록하려고 하는 유저가 일정을 만든 유저가 유효하지 않습니다.");
}
User managerUser = userRepository.findById(managerSaveRequest.getManagerUserId())
.orElseThrow(() -> new InvalidRequestException("등록하려고 하는 담당자 유저가 존재하지 않습니다."));
if (ObjectUtils.nullSafeEquals(user.getId(), managerUser.getId())) {
throw new InvalidRequestException("일정 작성자는 본인을 담당자로 등록할 수 없습니다.");
}
Manager newManagerUser = new Manager(managerUser, todo);
Manager savedManagerUser = managerRepository.save(newManagerUser);
return new ManagerSaveResponse(
savedManagerUser.getId(),
new UserResponse(managerUser.getId(), managerUser.getEmail())
);
}
▶ 마치며
- 이번 과제는 큰 어려움 없이 기존 진행됫던 강의와 세션들만 들으면 어렵지 않게 필수 단계를 해결할 수 있었다. 하지만 처음 세션을 수강할때 들었던 내용도 막상 과제를 진행할땐 도저히 이해가 안가, 세션과 강의를 수차례 돌려보기 일수였다. 이번 주차에선 과제를 진행하며 막힐때마다 세션을 돌려보느라 허송새월을 한 것 같다. 하지만 결국 이렇게 필수 과제를 완성한 것에 적지않은 성취감 또한 느껴진다. 느려도 이렇게 천천히 조금씩 배워나가는게 요즘 나름 즐겁게 느껴진다.
'TIL' 카테고리의 다른 글
| TIL 2025/02/12 (0) | 2025.02.13 |
|---|---|
| TIL - 2025/02/03 (0) | 2025.02.03 |
| TIL - 2025/01/17 (1) | 2025.01.17 |
| TIL 2024/01/09 (0) | 2025.01.10 |
| TIL 2025/01/08 (0) | 2025.01.08 |