Spring

커스텀 익셉션 적용

쭈녁 2024. 4. 28. 22:03

JPA를 사용하여 Entity를 찾아오거나 , 로직상에 문제가 있을 때 예외 처리를 할 일이 많이 있다.

예외처리를 할때 같은 케이스의 예외인데 메시지가 다르게 나가는 점과 같은 예외에서도 다른 HttpStatus 상태코드를 내보낼 상황이 있을 수 있다는 점에서 통일화하여  메시지와 반환 객체를 구조화할 필요성을 느꼈다.

 

따라서 아래와 같이 커스텀 예외와 예외 핸들링을 해보았다.

 

1. 예외 정의 Enum 타입 ErrorDefinition

@Getter
public enum ErrorDefinition {
    NOT_FOUND , ILLEGAL_ARGUMENT
}

 

2. ErrorCode Enum 타입

Enum 타입에 Http 상태 코드, 에러 분류 , 에러 메시지를 정의하였다.

@AllArgsConstructor
@Getter
public enum ErrorCode {
    // DB 조회 실패 에러 코드
    USER_NOT_FOUND(BAD_REQUEST, NOT_FOUND, "유저 정보를 찾을 수 없습니다."),
    REVIEW_NOT_FOUND(BAD_REQUEST, NOT_FOUND, "리뷰 정보를 찾을 수 없습니다."),
    MEDICINE_NOT_FOUND(BAD_REQUEST, NOT_FOUND, "영양제 정보를 찾을 수 없습니다."),
    IMAGE_NOT_FOUND(BAD_REQUEST, NOT_FOUND, "이미지 정보를 찾을 수 없습니다."),
    HASHTAG_NOT_FOUND(BAD_REQUEST, NOT_FOUND, "해쉬태그 정보를 찾을 수 없습니다."),

    // 로직 실패 에러코드
    REVIEW_DUPLICATION(BAD_REQUEST, ILLEGAL_ARGUMENT,"이미 후기를 작성한 영양제 입니다."),
    ACCESS_BLOCKED(FORBIDDEN, ILLEGAL_ARGUMENT,"작성자만 접근 가능합니다.")
    ;


    private final HttpStatus httpStatus;
    private final ErrorDefinition errorDefinition;
    private final String message;

}

3. 커스텀 예외 클래스

Enum 타입 ErrorCode 를 갖는 커스텀 예외를 만들었다. RuntimeException 을 상속받았다.

그리고 RuntimeException의 getMessage() 메서드를 오버라이드해서 ErrorCode의 메시지를 받을 수 있도록 하였다.

@AllArgsConstructor
@Getter
public class CustomException extends RuntimeException{
    ErrorCode errorCode;

    @Override
    public String getMessage() {
        return errorCode.getMessage();
    }
}

 

4. 에러 객체

일반 에러를 인자로 받아 ResponeEntity를 만들 수 있는 static 메서드와 커스텀 예외를 인자로 받을 수 있는 static 메서드를 오버로딩하여 활용도를 높였다. 

@Data
@AllArgsConstructor
@Builder
public class ErrorResult {
    private int status;
    private String code;
    private String detail;
    private String message;

    //모든 예외에서 사용가능
    public static ResponseEntity<ErrorResult> ofResponse(Exception e, HttpStatus httpStatus) {
        return ResponseEntity
                .status(httpStatus)
                .body(ErrorResult.builder()
                        .status(httpStatus.value())
                        .code(e.fillInStackTrace().toString())
                        .message(e.getMessage())
                        .build()
                );
    }

    // 커스텀에서 사용시
    public static ResponseEntity<ErrorResult> ofResponse(ErrorCode e) {
        return ResponseEntity
                .status(e.getHttpStatus())
                .body(ErrorResult.builder()
                        .status(e.getHttpStatus().value())
                        .code(e.getErrorDefinition().toString())
                        .detail(e.name())
                        .message(e.getMessage())
                        .build()
                );
    }
}

 

5. RestControllerAdvice

위에 만든 두 개의 static 메서드를 활용하는 예시

@RestControllerAdvice
@Order(1)
@Slf4j
public class GlobalCustomExceptionHandler {


    //커스텀 익셉션 사용 예
    @ExceptionHandler(CustomException.class)
    public ResponseEntity<ErrorResult> validException(CustomException e) {
        return ErrorResult.ofResponse(e.getErrorCode());
    }


    @ExceptionHandler(NullPointerException.class)
    public ResponseEntity<ErrorResult> nullPointException(NullPointerException e) {
        return ErrorResult.ofResponse(e, HttpStatus.valueOf(500));
    }
}