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);
}
}
잘잡아준다.
'Spring' 카테고리의 다른 글
[Spring] Eureka 서버 및 클라이언트 구축하기 (0) | 2025.03.04 |
---|---|
[Spring] Swagger UI 초기 세팅 (0) | 2025.01.26 |
[Spring] CustomExceptionHanlder (1) | 2024.11.29 |
[Spring] 검색 결과 미리보기 (0) | 2024.11.17 |
[Spring] 공개/비공개글 (2) | 2024.11.03 |