본문 바로가기

스프링 스터디

Controller 전역 예외 처리(Custom Exception) / 2022-02-10

사용자의 id가 없을 경우를 대비해 에러를 만들어서 멘트를 보여주자!고 하다가 다른 여러 개의 에러들을 custom 해봤다.

 

 

  • 똑같은 id를 가진 유저를 생성하려는 경우
  • 없는 id를 가진 유저를 찾는 경우

 

크게 두가지로 나누어졌다. 그외 나머지 에러는 handler를 통해 처리했다.

 

 

exception.ErrorCodeEnum

package com.second.spring_study.exception.ywoo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
@AllArgsConstructor
public enum ErrorCodeEnum {
    USER_NOT_FOUND(HttpStatus.NOT_FOUND.value(),"User Not Found"),
    USER_ALREADY_EXIST(HttpStatus.CONFLICT.value(), "User Already Exist");
    private final int status;
    private final String message;

}

 

커스텀할 에러들을 추가해주는 곳이다. 기본적으로 Http Error Code와 Error Message를 가진다. 각각 에러들의 경우에 따른 코드를 가져와 value()를 통해 int형으로 바꿔준다. 

 

 

 

exception.ApiException

package com.second.spring_study.exception.ywoo;

import lombok.Getter;

@Getter
public class ApiException extends RuntimeException{
    private final ErrorCodeEnum errorCodeEnum;

    public ApiException(ErrorCodeEnum e) {
        super(e.getMessage());
        this.errorCodeEnum=e;
    }
}

 

 

throw new ApiException()

나중에 에러를 생성할 때 클래스 이름?과 같은 역할을 한다. 이 파일을 호출해 에러를 처리한다.

 

 

exception.ApiExceptionAdvice

package com.second.spring_study.exception.ywoo;

import com.second.spring_study.dto.response.ywoo.ErrorResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;

@RestControllerAdvice
public class ApiExceptionAdvice {

    //생성자를 사용한 방법
    @ExceptionHandler({ApiException.class})
    protected ResponseEntity<ErrorResponse> exceptionHandler(HttpServletRequest request, final ApiException e){
        return new ResponseEntity<>(
                new ErrorResponse(
                e.getErrorCodeEnum().getStatus(),
                e.getErrorCodeEnum().getMessage()),
                HttpStatus.valueOf(e.getErrorCodeEnum().getStatus()));

    }

    //null pointer와 같은 기타 에러들
    @ExceptionHandler({Exception.class})
    protected ResponseEntity<ErrorResponse> ExceptionHandler(final NullPointerException e){
        return new ResponseEntity<>(new ErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(),e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
    }

    @ExceptionHandler({MethodArgumentNotValidException.class})
    protected ResponseEntity<ErrorResponse> MethodArgumentNotValidExceptionHandler(final MethodArgumentNotValidException e){
        return new ResponseEntity<>(new ErrorResponse(HttpStatus.BAD_REQUEST.value(),e.getMessage()), HttpStatus.BAD_REQUEST);
    }

    //SQL문이 안될때
    @ExceptionHandler({DataIntegrityViolationException.class})
    protected ResponseEntity<ErrorResponse> DataIntegrityViolationExceptionHandler(final DataIntegrityViolationException e){
        return new ResponseEntity<>(new ErrorResponse(HttpStatus.CONFLICT.value(),e.getMessage()), HttpStatus.CONFLICT);
    }

    //지원하지 않는 메서드
    @ExceptionHandler({HttpRequestMethodNotSupportedException.class})
    protected ResponseEntity<ErrorResponse> HttpRequestMethodNotSupportedExceptionHandler(final HttpRequestMethodNotSupportedException e){
        return new ResponseEntity<>(new ErrorResponse(HttpStatus.METHOD_NOT_ALLOWED.value(),e.getMessage()), HttpStatus.METHOD_NOT_ALLOWED);
    }
}

 

에러를 직접적으로 핸들링해주는 곳이다. 커스텀한 에러는 exceptionHandler를 통해 처리된다. 그외 에러가 나지만 경우가 많아 일일이 넣어주기 힘든 경우는 나머지와 같은 방식으로 처리했다. 가장 중요한건 첫번째 메서드!!

 

@ 어노테이션

  • @RestControllerAdvice
    • @ResponseBody + @ControllerAdvice =>@RestControllerAdvice
    • @ControllerAdvice 는 대부분 전역 예외처리를 위해 사용한다.
    • @ControllerAdvice 참고사항
  • @ExceptionHandler
    • @RestControllerAdvice가 있는 메서드 안에서 사용한다.
    • 넘겨받는 exception 클래스들의 종류에 따라 다르게 처리한다.

 

 

 

return 값을 생성자를 통해 줄 수도 있지만 builder를 사용할 수도 있다.

    @ExceptionHandler({ApiException.class})
    public ResponseEntity<ErrorResponse> exceptionHandler(HttpServletRequest request, final ApiException e){
        return ResponseEntity
                .status(e.getErrorCodeEnum().getStatus())
                .body(ErrorResponse.builder()
                        .status(e.getErrorCodeEnum().getStatus())
                        .message(e.getErrorCodeEnum().getMessage())
                        .build());

    }

 

 


 

에러들을 커스텀했으니 이제 각각의 경우에 맞는 곳에 사용해준다.

 

service.UserYwooService

    @Transactional
    public void createUser(UserRequestDto userRequestDto) {
        if(userRepository.existsByUserId(userRequestDto.getUserId())){
            throw new ApiException(ErrorCodeEnum.USER_ALREADY_EXIST);
        }
        UserYwoo user = UserYwoo.createUser(userRequestDto.getUserId(), userRequestDto.getUserName(), userRequestDto.getUserPassword());
        userRepository.save(user);
    }

첫번째로 회원을 생성하는 곳이다. 회원을 생성할 때 이미 id가 존재한다면 해당되는 에러를 사용해준다.

 

 

    @Transactional
    public void deleteUser(long id) {
        userRepository.findById(id).orElseThrow(() -> {
            throw new ApiException(ErrorCodeEnum.USER_NOT_FOUND);
        });
        userRepository.deleteById(id);
    }
    
        @Transactional
    public UserResponseDto findUser(long id){
        UserYwoo userYwoo = userRepository.findById(id).orElseThrow(()->{
            throw new ApiException(ErrorCodeEnum.USER_NOT_FOUND);
        });
        return UserResponseDto.of(userYwoo);
    }

    @Transactional
    public void updateUser(long id, UserRequestUpdateDto userRequestUpdateDto){
        userRepository.findById(id).orElseThrow(()->{
            throw new ApiException(ErrorCodeEnum.USER_NOT_FOUND);
        });
        updateUser(id,userRequestUpdateDto);
    }

그외 id가 존재해야만 할 수 있는 작업들은 USER_NOT_FOUND를 통해 없을 경우 알려준다.

 

 

 


그동안 자바를 사용하면서 에러가 나면 그저 에러가 났구나~하고 넘어갔던 것 같다. 특히 에러를 커스텀할 수 있다는 점이 새로웠다. 그저 서버 에러가 나면 난대로 에러를 보여줬었는데 클라이언트가 좀 더 이해하기 쉽도록 에러를 알려줄 수 있어서 좋은 것 같다. 모든 에러를 알 필요는 없겠지만 HTTP의 기본적인 에러 코드와 Exception들의 종류들을 공부해야할 필요가 있는 것 같다.

 

 

 


 

 

 

🔗 : ISSUES #26 Controller 예외 처리

🔗 : Spring Study