본문 바로가기
Trouble Shooting

Spring 심화 개인 과제 트러블 슈팅

by suyeoneee 2025. 1. 6.

이번 심화 주차 개인과제는

- (필수) 숙련 과제 필수사항 보완 및 기능 개선

- (도전) Spring Advanced Repository 의 코드 리팩토링 및 코드 포맷팅, N+1 문제, 테스트 코드 적용

으로 요구사항이 구성되어 있으며, 이전에 했던 과제처럼 서비스 로직을 구현하는 것이 아닌 주어진 기존 코드를 심층적으로 분석하고 이를 개선하는 데 중점을 두며, 코드의 품질을 높이는 작업에 초점을 맞추는 과제이다.

여기서, Lv 5-3 [인터셉터, AOP를 활용한 API 로깅] 부분은 이해도가 부족하여 더 공부하고 문제를 해결해야 할 것 같다💦💦


Lv2. [예외 처리 강화] 요구 사항의 키워드 중에서, GlobalExceptionHandler를 활용한 API 예외처리를 구현하고자 하였다. GlobalExceptionHandler를 실제로 구현해 본 경험이 없었어서.. 이번 기회에 한 번 써보자! 라는 마음 + 앞으로 진행될 과제의 예외처리 부분에서 참고할 수 있을 거 같기 때문에..!!

 

 

예외처리를 어떤 식으로 로직이 구성하면 되는 건지..  코드를 쳐본 경험이 없어서 막막했다. 

 

💥 Trouble. 핸들러가 뭔데.. ㅠ

사실.. "핸들러" 단어를 잘 쓰지 않아서 몰랐지 "특정 작업을 처리하는 코드"라는 포괄적인 개념이라고 생각하면 된다.

 

핸들러(Handler) 란

- 소프트웨어 개발에서 특정 작업, 요청, 이벤트 또는 데이터 처리를 담당하는 코드

- 이벤트 처리, 요청 처리, 또는 특정 동작을 처리하는 역할

 

과제를 할 때마다 만드는 컨트롤러, 사실 이것도 "핸들러" 이다. 클라이언트의 요청을 처리하는 핸들러.

// 유저 생성
@PostMapping("/sign-up")
public ResponseEntity<SignUpResponseDto> signUp(
        @RequestBody @Valid SignUpRequestDto requestDto
) {
    SignUpResponseDto signUpResponseDto = userService.signUp(
            requestDto.getEmail(),
            bcrypt.encode(requestDto.getPassword()),
            requestDto.getUserName()
    );

    return new ResponseEntity<>(signUpResponseDto, HttpStatus.CREATED);
}

 

 

GlobalExceptionHandler은 애플리케이션에서 발생하는 에러를 처리하는 역할을 한다.

스프링에서는 @ControllerAdvice 를 사용하여 글로벌 에러 처리를 한다.

 

💥 GlobalExceptionHandler 을 사용하여 어떻게 내가 원하는 에러처리를 구현할까?

 

내가 원하는 에러처리는 아래와 같이, 에러 코드와 함께 에러 메시지를 response body로 보내는 것이다.

{
    "errorCode": "ERR001",
    "errorMessage": "요청값의 형식이 맞지 않습니다."
}

 

우선, 에러코드와 에러메시지는 ENUM으로 관리하였다.

- ErrorCode.java

@Getter
public enum ErrorCode {
    UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "ERR001", "아이디 또는 비밀번호가 잘못되었습니다."),
    INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "ERR002", "올바르지 않은 입력값입니다."),
    METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "ERR003", "잘못된 HTTP 메서드입니다."),
    INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "ERR004", "서버 에러가 발생했습니다."),
    NOT_FOUND_USER(HttpStatus.NOT_FOUND, "ERR005", "존재하지 않는 유저입니다. "),
    NOT_FOUND_TODO(HttpStatus.NOT_FOUND, "ERR005", "존재하지 않는 일정입니다. "),
    NOT_FOUND_COMMENT(HttpStatus.NOT_FOUND, "ERR005", "존재하지 않는 댓글입니다. "),
    ACCESS_DENIED(HttpStatus.FORBIDDEN, "ERR006", "접근 권한이 없습니다.");

    private final HttpStatus status;
    private final String code;
    private final String message;

    ErrorCode(HttpStatus status, String code, String message) {
        this.status = status;
        this.code = code;
        this.message = message;
    }
}

 

 

Custom Exception 클래스 생성

- 에러처리 객체 (이 코드를 수정해서 다르게 커스텀할 수 있다)

@Getter
public class CustomException extends RuntimeException{
    private final ErrorCode errorCode;

    public CustomException(ErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
    }
}

 

 

GlobalExceptionHandler 클래스 생성

- ResponseEntity로 <에러코드, 에러메시지> 쌍을 보낸다.

@RestControllerAdvice // @ControllerAdvice + @ResponseBody : 전역적으로 예외를 처리하고 JSON 형식으로 응답을 반환하는 데 사용
public class GlobalExceptionHandler {

    @ExceptionHandler(CustomException.class)
    public ResponseEntity<Map<String, String>> handleCustomException(CustomException ex) {
        Map<String, String> response = new LinkedHashMap<>();
        response.put("errorCode", ex.getErrorCode().getCode());
        response.put("errorMessage", ex.getErrorCode().getMessage());

        log.info("에러 발생 >> 코드 : {}, 메시지 : {}", response.get("errorCode"), response.get("errorMessage"));
        return new ResponseEntity<>(response, ex.getErrorCode().getStatus());
    }
}

 

 

 

결과 (ex. 특정 유저 조회)

- 해당하는 유저가 없으면 다음과 같이 404 Httpstatus 와 함께 에러코드,메시지를 보낸다.

// 특정 유저 조회
public UserResponseDto findByUserId(Long userId) {
    User user = userRepository.findById(userId).
            orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER)); // NPE 방지
    return new UserResponseDto(user);
}

 

 

 

댓글