https://velog.io/@mooh2jj/SpringBoot-Exception-처리2 외에
Exception 처리에 대해서 customize한 Excetption 클래스들을 소개합니다. enum
을 추가해서 더 정리가 쉽게 할 수 있는 방법을 알려드립니다.
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;
}
결과 화면
이전 버전입니다.
@Getter
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class ErrorDetails {
private Date dateTime;
private String message;
private String description;
// enum 추가
private BlogErrorCode errorCode;
}
ErrorCode를 열거형으로 묶어서 정리할 수 있습니다.
@Getter
@RequiredArgsConstructor
public enum BlogErrorCode {
NO_TARGET("해당되는 대상이 없습니다."),
DUPLICATED_ID("Id가 중복되어 있습니다."),
INTERNAL_SERVER_ERROR("서버에 오류가 발생했습니다."),
INVALID_REQUEST("잘못된 요청입니다.")
;
private final String message;
}
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();
}
}
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;
}