본문 바로가기

[SpringBoot] 회원가입 기능시 필요한 @Valid / @ControllerAdvice (예외처리, 유효성 검사 설정)

인포꿀팁 발행일 : 2022-02-21

회원가입을 진행할 시에 처리해야 하는 유효성 검사와 예외처리들이 있다.

기본적으로 체크가 필요한 사항은

  1. 가입시 ID의 길이 제한
  2. ID 중복 여부 체크

가 있다.

예외처리를 하지않으면 클라이언트가 직접 에러페이지를 받게 된다.

만일 위에 2가지의 요구사항이 있을 경우 ID 길이 체크는 DB를 다녀가지 않아도 체크가 가능한 사항이다. 따라서 Spring에서 제공하는 Valid와 Validation을 통하여 사용자에서 넘어오는 값들의 유효성을 체크할 수 있다.

 

 

Spring Validation 을 활용한 유효성 검사

1. 디펜던시 추가

기존에는 boot에 기본적으로 가지고 있었으나 최근에는 따로 디펜던시를 잡아줘야한다고 한다.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.4.4</version>
</dependency>

 

2. 제약조건을 설정할 DTO 객체 클래스에 validation 어노테이션 설정

 

어노테이션 종류

Anotation 제약조건
@NotNull Null 불가
@Null Null만 입력 가능
@NotEmpty Null, 빈 문자열 불가
@NotBlank Null, 빈 문자열, 스페이스만 있는 문자열 불가
@Size(min=,max=) 문자열, 배열등의 크기가 만족하는가?
@Pattern(regex=) 정규식을 만족하는가?
@Max(숫자) 지정 값 이하인가?
@Min(숫자) 지정 값 이상인가
@Future 현재 보다 미래인가?
@Past 현재 보다 과거인가?
@Positive 양수만 가능
@PositiveOrZero 양수와 0만 가능
@Negative 음수만 가능
@NegativeOrZero 음수와 0만 가능
@Email 이메일 형식만 가능
@Digits(integer=, fraction = ) 대상 수가 지정된 정수와 소수 자리 수 보다 작은가?
@DecimalMax(value=)  지정된 값(실수) 이하인가?
@DecimalMin(value=) 지정된 값(실수) 이상인가?
@AssertFalse false 인가?
@AssertTrue true 인가?
package com.cos.photogramstart.web.dto.auth;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;

import com.cos.photogramstart.domain.user.User;
import lombok.Data;

@Data //getter setter
public class SignupRequestDTO {

    @Size(min=5, max=20)
    private String username;
    @NotBlank
    private String password;
    @NotBlank
    private String email;
    @NotBlank
    private String name;

    public User toEntity(){
        return User.builder()
            .username(username)
            .password(password)
            .email(email)
            .name(name)
            .build();
    }
}

 

3. 넘어오는 파라미터(인자)에 @Valid 어노테이션 설정 과 BindingResult 넣기

Controller 클래스에서 요청된 파라미터에 유효성 검사를 할 객체 앞에 @Valid를  작성 하고, 바로 뒤이어 BindingResult 를 작성하면 유효성 검사를 통한 결과를 담게된다.

  • @Valid 어노테이션을 통해 SignupRequestDTO가 유효한 객체인지 검사
  • SignupRequestDTO 객체가 유효하지 않으면 bindingResult.hasErrors() 메소드에서 true 값이 반환
  • SignupRequestDTO가 유효하지 않다면, 예외를 발생시켜 따로 처리하도록 설정 (이후 ControllerAdvice를 통해 예외처리 할 예정)
  /**
     * 회원가입 요청
     * @return 로그인 페이지
     */
    @PostMapping(value="/auth/signup")
    public String signup(@Valid SignupRequestDTO signupRequestDTO,BindingResult bindingResult) {
        if(bindingResult.hasErrors()){
            Map<String,String> errorMap = new HashMap<>();

            for(FieldError error: bindingResult.getFieldErrors()){
                errorMap.put(error.getField(),error.getDefaultMessage());
                System.out.println("#################################################################");
                System.out.println(error.getDefaultMessage());
                System.out.println("#################################################################");

            }
            throw new CustomValidationException("유효성 실패",errorMap);
        }else{
           //User <- SignupDto 
            User user = signupRequestDTO.toEntity();
            User userEntity = authService.signup(user);
            log.info("massage",userEntity);

            return "auth/signin";
        }

 

@ControllerAdvice 를 통한 예외처리

  • @Controller나 @RestController에서 발생한 예외를 한 곳에서 관리하고 처리할 수 있게 도와주는 어노테이션

1.  새로운 클래스 생성 후 @ControllerAdvice 어노테이션 설정

  • @ControllerAdvice 설정 이후, 설정할 예외에 @ExceptionHandler 설정
  • @ExceptionHandler(처리할 예외.class) ( 안에 value 를 적지않으면 모든 예외를 catch해 처리함 )
package com.cos.photogramstart.handler;

import java.util.Map;

import com.cos.photogramstart.handler.ex.CustomValidationException;
import com.cos.photogramstart.utils.Script;
import com.cos.photogramstart.web.dto.CRMRespDto;

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

@RestController
@ControllerAdvice // 모든 exception을 가로채온다.
public class ControllerExceptionHandler {
    
    /*
    @ExceptionHandler(CustomValidationException.class)
    public CRMRespDto<?> validationException(CustomValidationException e){
        return new CRMRespDto<Map<String,String>>(-1,e.getMessage(),e.getErrorMap());
    }*/

    /**
     * 예외처리 상황에서 스크립트로 응답
     * 1. 클라이언트 응답시엔 script
     * 2. Ajax 통신에는 위의 방법이 나음
     * @param e
     * @return 에러메세지 스크립트로 반환
     */
    @ExceptionHandler(CustomValidationException.class)
    public String validationException(CustomValidationException e){
        return Script.back(e.getErrorMap().toString());

    }
}

 

2.  사용자 정의  Exception 처리

  • 처리할 RuntimeException을 상속받은 클래스를 생성한다.
  • 원하는 형태의 데이터로 반환 받기 위함 (회원가입시 여러 파트에서 예외가 발생한 경우)
  • 요구사항에 따라 데이터 형식이 아닌 script를 반환해 사용자에게 직접 알림을 보낼 수도 있다.(비권장방식)
package com.cos.photogramstart.handler.ex;

import java.util.Map;

public class CustomValidationException extends RuntimeException{

    private static final long serialVersionUID = 1L;

    private String message;
    private Map<String,String> errorMap;
    
    public CustomValidationException(String message, Map<String,String> errorMap){
        super(message); // 부모의 클래스에 전달해준다.
        this.errorMap= errorMap;
    }

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

반환 데이터

{
    "code": -1,
    "message": "유효성 실패",
    "data": {
    	"username": "크기가 5에서 20 사이여야 합니다"
	}
}
package com.cos.photogramstart.utils;

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


}

 

댓글