SpringBoot Exception 처리3 - enum 적용

devdo·2022년 3월 24일
0

SpringBoot

목록 보기
21/35
post-thumbnail

https://velog.io/@mooh2jj/SpringBoot-Exception-처리2 외에
Exception 처리에 대해서 customize한 Excetption 클래스들을 소개합니다. enum을 추가해서 더 정리가 쉽게 할 수 있는 방법을 알려드립니다.


완성 ExceptionHandler 정리 (2022-12-31)

MemoAPIException 하나로 통일하고 exceptionHandler에서 errorCode로 switch하는 방식입니다.

MemoAPIException

@Getter
public class MemoAPIException extends RuntimeException {

    private String entityName;
    private String fieldName;
    private final ErrorCode errorCode;
    
    public MemoAPIException(ErrorCode errorCode, String entityName, String fieldName, long fieldValue) {
        super(String.format("%s not found with %s : %d", entityName, fieldName, fieldValue));
        this.entityName = entityName;
        this.fieldName = fieldName;
        this.errorCode = errorCode;
    }
	// fieldValue가 필요없는 exception 처리
    public MemoAPIException(ErrorCode errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

}

MemoExceptionHandler

@Slf4j
@RestControllerAdvice
public class MemoExceptionHandler extends ResponseEntityExceptionHandler {
	// MemoAPIException 하나로 통일 errorCode로 switch하는 방식
    @ExceptionHandler(MemoAPIException.class)
    public ResponseEntity<Object> handleExceptionInternal(
            MemoAPIException ex,
            WebRequest request) {
        log.error("MemoAPIException: ",ex);

        ErrorDetails errorDetails = ErrorDetails.builder()
                .dateTime(LocalDateTime.now())
                .message(ex.getMessage())
                .details(request.getDescription(false))
                .errorCode(ex.getErrorCode())
                .errorMsg(ex.getErrorCode().getErrorMsg())
                .build();

        return new ResponseEntity<>(errorDetails, mapToStatus(errorDetails.getErrorCode()));
    }

    private HttpStatus mapToStatus(ErrorCode errorCode) {

        switch (errorCode) {
            case NO_FOUND_ENTITY:
                return HttpStatus.NOT_FOUND;
            case DUPLICATED_ENTITY:
                return HttpStatus.CONFLICT;
            case INVALID_REQUEST:
                return HttpStatus.BAD_REQUEST;
            case NOT_FOUND_USER:
                return HttpStatus.UNAUTHORIZED;
            default: // 나머지는 모두 INTERNAL_SERVER_ERROR 처리
                return HttpStatus.INTERNAL_SERVER_ERROR;
        }
    }
}

ErrorDetails

@NoArgsConstructor
@Getter
public class ErrorDetails {

    private LocalDateTime dateTime;
    private String message;
    private String details;

    private ErrorCode errorCode;
    private String errorMsg;

    @Builder
    public ErrorDetails(LocalDateTime dateTime, String message, String details, ErrorCode errorCode, String errorMsg) {
        this.dateTime = dateTime;
        this.message = message;
        this.details = details;
        this.errorCode = errorCode;
        this.errorMsg = errorMsg;
    }
}

ErrorCode

@Getter
@RequiredArgsConstructor
public enum ErrorCode {

    NO_FOUND_ENTITY("존재하지 않는 엔티티입니다."),
    DUPLICATED_ENTITY("이미 존재하는 엔티티입니다."),
    INVALID_REQUEST("요청한 값이 올바르지 않습니다."),
    NOT_FOUND_USER("존재하지 않는 사용자입니다."),
    INTERNAL_SERVER_ERROR("일시적인 오류가 발생했습니다. 잠시 후 다시 시도해주세요.");  // 장애 상황

    private final String errorMsg;

}

ServiceImpl

    @Transactional(readOnly = true)
    @Override
    public MemoResponse getById(Long memoId) {

        Memo memo = memoRepository.findById(memoId)
                .orElseThrow(() -> new MemoAPIException(ErrorCode.NO_FOUND_ENTITY, "memo", "memeId", memoId));
        MemoResponse memoResponse = MemoResponse.toDto(memo);
        log.info("memoResponse: {}", memoResponse);
        return memoResponse;
    }

결과 화면



이전 버전입니다.

ErrorDetails

@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ErrorDetails {

    private Date dateTime;
    private String message;
    private String description;
    // enum 추가
    private BlogErrorCode errorCode;
}

BlogErrorCode

ErrorCode를 열거형으로 묶어서 정리할 수 있습니다.

@Getter
@RequiredArgsConstructor
public enum BlogErrorCode {

    NO_TARGET("해당되는 대상이 없습니다."),
    DUPLICATED_ID("Id가 중복되어 있습니다."),

    INTERNAL_SERVER_ERROR("서버에 오류가 발생했습니다."),
    INVALID_REQUEST("잘못된 요청입니다.")
    ;

    private final String message;

}

RuntimeException 상속한 커스터마이징한 Exception들

1) ResourceNotFoundException(NotFound용 Exception)

@Getter
public class ResourceNotFoundException extends RuntimeException{

    private BlogErrorCode errorCode;
    private String detailMessage;

    public ResourceNotFoundException(BlogErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
        this.detailMessage = errorCode.getMessage();
    }

}

2) BlogAPIException

@Getter
public class BlogAPIException extends RuntimeException {

    private HttpStatus status;

    private BlogErrorCode errorCode;

    public BlogAPIException(BlogErrorCode errorCode) {
        super(errorCode.getMessage());
        this.errorCode = errorCode;
        this.message = errorCode.getMessage();
    }

}

ExceptionHandler

BlogExceptionHandler

Specific: @ExceptionHandler에서 특정 Exception 클래스를 골라주면 됩니다.
Gloabl : @ExceptionHandler에서 Exception 클래스만 골라주면 됩니다.

✅Exception 처리2 에서 다른 점은 ErrorDetails 안에 에러코드를 열거형으로 묶어든 enum을 장착한다는 것입니다.

@ControllerAdvice
public class BlogExceptionHandler extends ResponseEntityExceptionHandler {

   // handle sepecific exceptions
    @ExceptionHandler(ResourceNotFoundException.class)
    public ResponseEntity<ErrorDetails> handleResourceNotFoundException(
            ResourceNotFoundException exception,
            WebRequest webRequest){

        ErrorDetails errorDetails = ErrorDetails.builder()
                .timestamp(new Date())
                .message(exception.getMessage())
                .description(webRequest.getDescription(false))
                .errorCode(exception.getErrorCode())
                .build();

        return new ResponseEntity<>(errorDetails, HttpStatus.NOT_FOUND);
    }

    @ExceptionHandler(BlogAPIException.class)
    public ResponseEntity<ErrorDetails> handleBlogAPIException(
            BlogAPIException exception,
            WebRequest webRequest){

        ErrorDetails errorDetails = ErrorDetails.builder()
                .timestamp(new Date())
                .message(exception.getMessage())
                .description(webRequest.getDescription(false))
                .errorCode(exception.getErrorCode())
                .build();

        return new ResponseEntity<>(errorDetails, HttpStatus.BAD_REQUEST);
    }

 	// global exception
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ErrorDetails> handleGlobalException(
            Exception exception,
            WebRequest webRequest){

        ErrorDetails errorDetails = ErrorDetails.builder()
                .timestamp(new Date())
                .message(exception.getMessage())
                .description(webRequest.getDescription(false))
                .errorCode(INTERNAL_SERVER_ERROR)
                .build();

        return new ResponseEntity<>(errorDetails, HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

BlogServiceImpl

	// Exception 처리 메서드
    private Comment errorCheckComment(BoardRepository boardRepository, Long boardId, CommentRepository commentRepository, Long commentId) {
        Board board = boardRepository.findById(boardId)
                .orElseThrow(() -> new ResourceNotFoundException(BlogErrorCode.NO_TARGET));

        Comment comment = commentRepository.findById(commentId)
                .orElseThrow(() -> new ResourceNotFoundException(BlogErrorCode.NO_TARGET));

        if (!comment.getBoard().getId().equals(board.getId())) {
            throw new BlogAPIException(BlogErrorCode.NO_TARGET);
        }
        return comment;
    }


참고

profile
배운 것을 기록합니다.

0개의 댓글