spring-boot 예외처리(Exception)

leekyungryul·2023년 12월 5일

spring-boot-project

목록 보기
3/5

db까지 전달되기 전에 처리되는 전처리

의존성 주입

maven repository

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>3.2.0</version>
</dependency>

// https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-validation
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '3.2.0'

컨트롤러의 함수의 입력받는 매개변수에 @Valid 어노테이션 추가

@PostMapping("/auth/signup")
    public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) {

검증이 필요한 객체의 컬럼에 어노테이션 추가

@Data
public class SignupDto {
    @Size(min = 2, max = 20)
    @NotBlank
    private String username;
    @NotBlank
    private String password;
    @NotBlank
    private String email;
    @NotBlank
    private String name;

SignupDto 객체에서 username, password, email, name에 대하여 추가함

db에서 확인해서 처리되는 후처리

@NoArgsConstructor
@AllArgsConstructor
@Data
@Entity // DB에 테이블을 생성
@Table(name = "user") // user 테이블을 만들어줌
public class User {

    @Id // Primary Key
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 번호 증가 전략이 데이터베이스를 따라감
    private int id;
    @Column(length = 20, unique = true)
    private String username;
    @Column(nullable = false)
    private String password;
    @Column(nullable = false)
    private String email;
    @Column(nullable = false)
    private String name;
    private String website; // 웹사이트

각 컬럼에 validation 요구사항을 추가

에러코드 확인

    @PostMapping("/auth/signup")
    public String signup(@Valid SignupDto signupDto, BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            Map<String, String> errorMap = new HashMap<>();
            bindingResult.getFieldErrors().forEach(error -> {
                errorMap.put(error.getField(), error.getDefaultMessage());
            });
        }

예외처리를 위한 controllerAdvice 사용

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(RuntimeException.class)
    public String validationException(RuntimeException e) {
        return e.getMessage();
    }
}

위와같이 @ControllerAdvice 어노테이션을 사용하면 모든 예외를 핸들링 가능하다.
validationException함수는 모든 RuntimeException을 핸들링한다.

그러나 위와같이 사용하면 컨트롤러의 signup함수에서 발생한 에러들의 defaultMessage를 모은 errorMap을 전달할 수 없다.

RuntimeExcepttion을 상속받아서 custom한 객체 생성

public class CustomValidationException extends RuntimeException {

        // 객체를 구분할 때 사용하는 값
        private static final long serialVersionUID = 1L;

        private Map<String, String> errorMap;

        public CustomValidationException(String massege, Map<String, String> errorMap) {
            super(massege);
            this.errorMap = errorMap;
        }

        public Map<String, String> getErrorMap() {
            return errorMap;
        }
}

위의 코드에서 message는 클래스에서 가지고 있을 필요가 없다
왜냐하면 상위 클래스로 던지면 상위클래스가 가지고 있다.

exceptionHandler 수정

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(CustomValidationException.class)
    public Map<String, String> validationException(CustomValidationException e) {
        return e.getErrorMap();
    }
}

runtimeException객체를 상속받은 CustomValidationException객체를 인자로 받아서 errorMap을 리턴한다.

공통 dto 생성

errorMap뿐 아니라 message등 기타정보들로 함께 return할 수 있도록 공통 dto를 생성한다.

@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto {

private String message;
private Map<String, String> errorMap;

}

ControllerExceptionHandler 수정

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(CustomValidationException.class)
    public CMRespDto validationException(CustomValidationException e) {
        return new CMRespDto(e.getMessage(), e.getErrorMap());
    }
}
{
    "message": "유효성 검사 실패함",
    "errorMap": {
        "name": "공백일 수 없습니다",
        "email": "공백일 수 없습니다",
        "username": "크기가 2에서 20 사이여야 합니다"
    }
}

위와같이 응답을 확인할 수 있다.

응답 dto를 공통으로 사용가능하도록 수정

@AllArgsConstructor
@NoArgsConstructor
@Data
public class CMRespDto<T> {
    private int code; // 1(성공), -1(실패)
    private String message;
    private T data;
}

위와같이 제네릭을 사용해서 수정하게 되면 데이터의 유형에 상관없이 받을 수 있다.

ControllerExceptionHandler도 수정

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(CustomValidationException.class)
    public CMRespDto validationException(CustomValidationException e) {
        return new CMRespDto(-1, e.getMessage(), e.getErrorMap());
    }
}

원래페이지에서 에러페이지로 이동하지 않고 에러창만 만들어서 반환하기

public class Script {

    public static String back(String message) {
        StringBuffer sb = new StringBuffer();
        sb.append("<script>");
        sb.append("alert('").append(message).append("');");
        sb.append("history.back();");
        sb.append("</script>");
        return sb.toString();
    }
}

위와 같이 스크립트를 반환하는 클래스를 생성

ControllerExceptionHandler수정

@RestController
@ControllerAdvice
public class ControllerExceptionHandler {

    @ExceptionHandler(CustomValidationException.class)
    public String validationException(CustomValidationException e) {
        return Script.back(e.getErrorMap().toString());
    }
}
profile
끊임없이 노력하는 개발자

0개의 댓글