TIL

TIL 2025/02/26

호롤로 2025. 2. 27. 02:32

▶ 오늘 한일

- 오늘은 차주 목요일에 발재된 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