[Spring] Spring-advanced 트러블 슈팅

조민경·2025년 2월 25일
0

Spring

목록 보기
4/13

🌐 github 주소
https://github.com/JoeMinKyung/spring-advanced

레벨 1-1

if 문passwordEncoder.encode() 호출보다 위로 이동하여, 불필요한 인코딩이 발생하지 않도록 리팩토링 했다. 이제 이메일 중복 검사를 먼저 수행한 후, 패스워드를 인코딩하도록 수정되었다.

수정 전

if (userRepository.existsByEmail(signupRequest.getEmail())) {
    throw new InvalidRequestException("이미 존재하는 이메일입니다.");
}

수정 후

@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());

        User newUser = new User(
                signupRequest.getEmail(),
                encodedPassword,
                userRole
        );
        User savedUser = userRepository.save(newUser);

        String bearerToken = jwtUtil.createToken(savedUser.getId(), savedUser.getEmail(), userRole);

        return new SignupResponse(bearerToken);
    }

레벨 1-2

불필요한 else 블록을 제거하고 if 문을 사용하여 각 조건이 독립적으로 처리된다. 또한 불필요한 중첩이 사라져 코드가 더 깔끔해졌다.

수정 전

WeatherDto[] weatherArray = responseEntity.getBody();
if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
    throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode());
} else {
    if (weatherArray == null || weatherArray.length == 0) {
        throw new ServerException("날씨 데이터가 없습니다.");
    }
}

수정 후

if (!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
    throw new ServerException("날씨 데이터를 가져오는데 실패했습니다. 상태 코드: " + responseEntity.getStatusCode());
}

if (weatherArray == null || weatherArray.length == 0) {
    throw new ServerException("날씨 데이터가 없습니다.");
}

레벨 1-3

changePassword() 메서드에서 비밀번호 검증 로직을 제거하고 대신 UserChangePasswordRequestDto에서 spring-boot-starter-validation 라이브러리를 활용하여 유효성 검사를 수행하도록 개선하였다.

수정 전

if (userChangePasswordRequest.getNewPassword().length() < 8 ||
        !userChangePasswordRequest.getNewPassword().matches(".*\\d.*") ||
        !userChangePasswordRequest.getNewPassword().matches(".*[A-Z].*")) {
    throw new InvalidRequestException("새 비밀번호는 8자 이상이어야 하고, 숫자와 대문자를 포함해야 합니다.");
}

수정 후

@Getter
public class UserChangePasswordRequestDto {

    @NotBlank(message = "기존 비밀번호를 입력해야 합니다.")
    private String oldPassword;

    @NotBlank(message = "새 비밀번호를 입력해야 합니다.")
    @Size(min = 8, message = "새 비밀번호는 최소 8자 이상이어야 합니다.")
    @Pattern(regexp = ".*\\d.*", message = "새 비밀번호에는 적어도 하나의 숫자가 포함되어야 합니다.")
    @Pattern(regexp = ".*[A-Z].*", message = "새 비밀번호에는 적어도 하나의 대문자가 포함되어야 합니다.")
    private String newPassword;
}

userChangePasswordRequestDto에서 javax.validation의 어노테이션을 활용하여 유효성 검사를 수행했다. 이 Dto에서 검증을 담당하므로 changePassword() 메서드에서 관련 검증 로직을 제거했다.


@PostMapping("/change-password")
public ResponseEntity<Void> changePassword(@RequestParam long userId, 
                                           @Valid @RequestBody UserChangePasswordRequest request) {
    userService.changePassword(userId, request);
    return ResponseEntity.ok().build();
}

UserChangePasswordRequest의 유효성 검사의 작동을 위해 컨트롤러에서도 @Valid를 적용했다.


레벨 2

@EntityGraph를 사용하여 fetch join과 동일한 효과를 내도록 TodoRepository를 수정하였다.

@EntityGraph(attributePaths = "user")

TodoRepositoryfindAllByOrderByModifiedAtDesc 메서드에서 @EntityGraph를 사용하여 User 엔티티를 함께 가져오도록 설정했다. 즉 Todo 엔티티 조회 시 user 정보를 함께 가져오며, 기존 fetch join과 동일한 효과를 내며, N+1 문제를 방지할 수 있다.


레벨 3-1

테스트 코드의 경우 계속 실행이 안돼서 결과를 확인하지 못했다.. 오늘 해설 세션을 들으며 어디가 잘못됐는지 확인할 예정이다.

private final PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();

테스트 코드에서 PasswordEncoder가 올바르게 설정되지 않아 BCryptPasswordEncoder를 직접 생성하여 PasswordEncoder를 주입받지 않고도 테스트가 정상적으로 동작하도록 수정하였다.


레벨 3-2

1번 케이스

@Test
    public void manager_목록_조회_시_Todo가_없다면_InvalidRequestException을_던진다() {
        // given
        long todoId = 1L;
        given(todoRepository.findById(todoId)).willReturn(Optional.empty());

        // when & then
        assertThrows(InvalidRequestException.class,
                () -> managerService.getManagers(todoId),
                "Manager not found"
        );
    }

테스트 메서드 명을 변경하였고, assertThrows 문을 수정하였다.


2번 케이스

InvalidRequestException exception = assertThrows(
                InvalidRequestException.class,
                () -> commentService.saveComment(authUser, todoId, request)
        );

ServerException 대신 InvalidRequestException으로 수정하였다.


3번 케이스

if (todo.getUser() == null) {
            throw new InvalidRequestException("todo의 user가 null입니다.");
        }

ManagerServicesaveManager에서 todo.getUser()null인지 검사하는 로직을 추가하였다. null이라면 InvalidRequestException을 던진다.

0개의 댓글