본문 바로가기
게시판 만들기(Spring Boot, JPA)

게시판 만들기 06 - 예외처리

by burpee 2023. 4. 5.

저번 포스팅까지 해서 게시판 CRUD, 페이징 기능을 구현했다. 그리고 해당 기능에 대한 테스트 코드도 작성했다. 이제는 이 기능들이 작동할 때 발생할 수 있는 예외들을 처리해야 한다.

이번 포스팅은 프로젝트 전역에서 발생할 수 있는 예외를 잡아주는 [ic]@ControllerAdvice [/ic]를 알아보고 예외 처리를 할 때 사용해보려고 한다.

예외처리를 하는 이유

프로그램을 만들다보면 많은 오류들이 발생한다. 개발자의 예상과 달리 예기치 못하게 발생하는 일들을 예외라고 하고, 이 예외를 대비하고 준비하는 것을 예외 처리라고 한다. 예외 처리를 잘한다면, 프로그램에 예상하지 못한 일이 발생했을 때 비정상적으로 종료되지 않고 적절한 조치를 하고 정상 상태를 유지할 수 있다.

 

@ControllerAdvice

메소드 내부에서 [ic] try catch [/ic] 문을 사용하거나 [ic]@ExceptionHandler [/ic]를 이용하여 예외를 처리해 줄 수 있지만, 이 프로젝트에서는 [ic]@ControllerAdvice [/ic]를 사용하려고 한다. [ic]@ControllerAdvice [/ic]는 모든 Controller에서 발생하는 예외를 전역으로 잡아서 처리해 줄 수 있는 어노테이션이다. 

@ControllerAdvice
public class ExceptionController {
	// 예외 처리 메소드 작성
}

이렇게 [ic]@ControllerAdvice[/ic] 어노테이션을 붙인 클래스 파일을 만들어서 사용하면 된다. 그리고 내부에 [ic]@ExceptionHandler [/ic] 어노테이션을 붙인 메서드를 만들어서 [ic]@ExceptionHandler [/ic]에 정의한 예외가 발생했을 때 예외 처리를 해줄 수 있다.

@ControllerAdvice
public class ExceptionController {
    // 예외 처리 메소드 작성
    @ExceptionHandler(Exception.class)
    public String exception(Exception e) {
    	return "예외 발생";
    }
}

우리가 게시판 rest api를 만들면서 클라이언트에 데이터를 내려줄 때 ResponseEntity로 감싸서 내려주었다. 예외 처리 후에도 동일하게 에러 데이터 객체를 만들고 ResponseEntity로 감싸서 클라이언트로 내려주려고 한다. 이 때 [ic]@ResponseBody [/ic] 어노테이션을 사용하면 예외처리 메서드를 rest 하게 만들 수 있다.

@ControllerAdvice
public class ExceptionController {
    // 예외 처리 메소드 작성
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public ResponseEntity<ResponseErrorDto> invalidRequest(Exception e) {
    	// 에러 객체 만들고 Responseentity로 감싸주기
        return responseEntity;
    }
}

이제 게시판 프로젝트에서 예외처리를 시작해보자.

예외 만들어서 throw 하기

예외 처리를 하려면 일단 예외가 발생해야 한다. 먼저 예외를 만들어보자. 이 전에 게시글 단건조회 api를 작성할 때 잘못된 id로 게시글을 조회하면 RuntimeException을 throw 하는 코드를 작성했다.

Post post = postRepository.findById(postId)
                .orElseThrow(() -> new RuntimeException());

실행 중에 발생한 예외이기 때문에 RuntimeException을 사용해도 되지만, id에 해당하는 게시글을 찾지 못했을 때 예외가 발생하므로 PostNotFoundException이라는 사용자 정의 예외를 만들어서 사용하려고 한다.

 

PostNotFoundException

public class PostNotFoundException extends RuntimeException {

    private static final String MESSAGE = "존재하지 않는 게시글입니다.";

    public PostNotFoundException() {
        super(MESSAGE);
    }
}

사용자 정의 예외를 따로 만들어주고 방금 전 throw 부분을 수정해 준다.

Post post = postRepository.findById(postId)
                .orElseThrow(() -> new PostNotFoundException());

 

예외 DTO 만들기

예외가 발생했을 때 예외의 내용들을 클라이언트에게 내려주려고 한다. 이때 예외의 내용들을 정리해서 담을 DTO 클래스를 만들 것이다. 예외에 대한 응답을 하나의 DTO에 담아서 하게 되면 클라이언트가 통일된 구조의 예외 응답을 받을 수 있기 때문에 이 방식을 선택했다.

 

ResponseErrorDto

@Getter
public class ResponseErrorDto {

    private final String code;

    private final String message;

    private final Map<String, String> validation;

    @Builder
    public ResponseErrorDto(String code, String message, Map<String, String> validation) {
        this.code = code;
        this.message = message;
        this.validation = validation;
    }

    public void addValidation(String fieldName, String errorMessage) {
        this.validation.put(fieldName, errorMessage);
    }
}

DTO는 HTTP 상태 코드를 담을 [ic] code [/ic], 예외 메시지를 담을 [ic] message [/ic], 데이터 검증에 대한 내용을 담을 [ic] validation [/ic]을 필드로 가지고 있다.

ControllerAdvice에서 예외 처리

이제 [ic]@ControllerAdvice [/ic]를 사용해 예외 처리를 하기 위한 준비가 다 됐다. 예외 처리 코드를 작성해 보자.

@Slf4j
@ControllerAdvice
public class ExceptionController {
    @ResponseBody
    @ExceptionHandler(PostNotFound.class)
    public ResponseEntity<ResponseErrorDto> postNotFound(PostNotFound e) {
        ResponseErrorDto responseErrorDto = ResponseErrorDto.builder()
                .code(String.valueOf(HttpStatus.NOT_FOUND.value()))
                .message(e.getMessage())
                .build();

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(responseErrorDto);
    }
}

첫 번째로 [ic]@ExceptionHandler [/ic]를 사용해서 처리하고 싶은 예외의 클래스를 넣어준다. 두 번째로 해당 예외에 있는 내용들을 사용해서 ResponseErrorDto라는 예외 응답 공통 객체를 생성해 준다. 세 번째로 만든 DTO를 ResponseEntity로 감싸서 클라이언트로 반환한다. 이때 HTTP 상태는 예외의 상황에 맞게끔 넣어준다. 위의 예시는 Not Found 상태 코드를 반환한다.

 

예외 응답

예외 응답

존재하지 않는 id를 요청한 결과를 포스트맨에서 확인해 봤다.


소스코드

https://github.com/cheoljin408/simple-board-api

댓글