개발 ON
  • [Spring] Rest API ExceptionHandler
    2025년 01월 19일 23시 28분 28초에 업로드 된 글입니다.
    작성자: 이주여이

    Rest API 서버 만들면서 예외 처리에 대한 부분이 필요했다.

     

    설명은 없고 메모용이라 간략간략하게 확인만 하자.

     

    필자는 STS4(Spring Tool Suite 4)에서 진행한다.

     

    패키지 구조는 아래와 같다.

     

     

    @Getter
    @AllArgsConstructor
    public enum ApiStatus {
        SUCCESS(200, "요청이 성공적으로 완료되었습니다."),
        CREATED(201, "리소스가 성공적으로 생성되었습니다."),
        BAD_REQUEST(400, "요청이 유효하지 않습니다."),
        UNAUTHORIZED(401, "권한이 확인되지 않았습니다."),
        FORBIDDEN(403, "접근이 거부되었습니다."),
        NOT_FOUND(404, "요청한 리소스를 찾을 수 없습니다."),
        REQUIRED_ERROR(422, "필수 값이 누락되었습니다."),
        INTERNAL_SERVER_ERROR(500, "서버에서 예기치 못한 오류가 발생했습니다.");
    
        private final int statusCode;
        private final String message;
    }

     

    예외 던질 때 담을 HTTP 상태코드 및 메세지가 담겨있다.

     

    @Data
    @Builder
    public class ApiResponse<T> {
        private int status;
        private String message;
    
        @Builder.Default // builder 패턴으로 객체 생성 시 기본 값 지정
        private String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    
        private T data;
    
        @Builder.Default
        private List<String> errors = List.of();
    
        public static <T> ApiResponse<T> success(T data) {
            return ApiResponse.<T>builder()
                    .status(ApiStatus.SUCCESS.getStatusCode())
                    .message(ApiStatus.SUCCESS.getMessage())
                    .data(data)
                    .build();
        }
    
        public static <T> ApiResponse<T> error(int status, String message, List<String> errors) {
            return ApiResponse.<T>builder()
                    .status(status)
                    .message(message)
                    .errors(errors)
                    .build();
        }
    }

     

    ApiResponse는 ResponseEntity의 지네릭 타입에 명시할 클래스이다.

     

    status는 ApiStatus의 HTTP 상태 코드를, message는 ApiStatus에 있는 메세지를, timestamp는 API 호출 시 호출한 시간을 찍어내기 위함이며 data는 return할 데이터이다.

     

    성공 시에는 상태 코드랑 메세지가 동일하기에 미리 적어두었으며 data(VO)만 받아 객체를 생성한다. 반대로 실패 시에는 해당 객체 생성 시 인자 값으로 담은 상태 코드와 메세지, 에러를 받는다.

     

    @Getter
    public class ApiException extends RuntimeException {
        private ApiStatus apiStatus;
    
        public ApiException(ApiStatus apiStatus) {
            this.apiStatus = apiStatus;
        }
    }

     

    직접 만든 예외다.

     

    해당 예외를 던질 때 해당 값은 ApiStatus(ENUM)에 있는 값들 중 하나를 고를 수 있다.

     

    @RestControllerAdvice
    public class ApiExceptionAdvice {
        @ExceptionHandler(ApiException.class)
        public ResponseEntity<ApiResponse<Object>> customExceptionHandler(HttpServletRequest request, final ApiException e) {
            ApiResponse<Object> apiResponse = ApiResponse.<Object>builder()
                    .status(e.getApiStatus().getStatusCode())
                    .message(e.getApiStatus().getMessage())
                    .errors(null)
                    .build();
    
            return ResponseEntity
                    .ok(apiResponse);
        }
    
        @ExceptionHandler(DataIntegrityViolationException.class)
        public ResponseEntity<ApiResponse<Object>> dataIntegrityViolationExceptionHandler(HttpServletRequest request, final DataIntegrityViolationException e) {
            ApiResponse<Object> apiResponse = ApiResponse.<Object>builder()
                    .status(ApiStatus.REQUIRED_ERROR.getStatusCode())
                    .message(ApiStatus.REQUIRED_ERROR.getMessage())
                    .errors(null)
                    .build();
    
            return ResponseEntity
                    .ok(apiResponse);
        }
    }

     

    위에 있는 customExceptionHandler는 내가 만들어놓은 ApiException을 던졌을 때 받기위한 메소드이며 dataIntegerityViolationExceptionHanlder는 fk 없이 INSERT 때릴 때 INSERT 때리기도 전에 해당 예외가 터지길래 추가해봤다. 물론 Vaildation 사용해서 작업을 해야하는 부분이지만 귀찮아서 안했으므로 땜빵용이다.

     

    errors는 나중에 작업할꺼다.

     

    이제 테스트해보자.

     

        @PostMapping
        public ResponseEntity<ApiResponse<Object>> insBoard(BoardInsDto dto) {
            int insBoardRows = service.insBoard(dto);
    
            if (Util.isNull(insBoardRows)) {
                throw new ApiException(ApiStatus.REQUIRED_ERROR);
            } else {
                int result = dto.getIboard();
                ApiResponse<Object> apiResponse = ApiResponse.success(result);
    
                return ResponseEntity.ok(apiResponse);
            }
        }

     

     

    잘잡아준다.

    'Study > Spring' 카테고리의 다른 글

    [Spring] Swagger UI 초기 세팅  (0) 2025.01.26
    [Spring] CustomExceptionHanlder  (0) 2024.11.29
    [Spring] 검색 결과 미리보기  (0) 2024.11.17
    [Spring] 공개/비공개글  (2) 2024.11.03
    [Spring] RSS(XML) Parsing  (0) 2024.09.21
    댓글