Spring Boot: 유효성 검증 Validation

김아무개·2023년 6월 4일
0

Spring Boot 🍃

목록 보기
26/95
post-custom-banner

보고 배운 사이트 : mangkyu.tistory + ChatGPT + 인강


검증 어노테이션

객체의 유효성을 검증하기 위해 사용되는 어노테이션이다.

이것들은 주로
클래스의 필드나, 메서드 매개변수에 적용하여
데이터의 유효성을 정의하고 검사한다.

다양한 검증 어노테이션들이 있으며,
몇 가지 대표적인 어노테이션들을 살펴본다.

@NotNull

null 불가를 의미

@NotEmpty

null , "" 불가를 의미

@NotBlank

null , "" , " " 불가를 의미

@Size

int Type 불가
문자 길이 측정
@Size(min = 6, max = 6)

@Past

과거 날짜

@PastOrPresent

오늘이거나 과거 날짜

@Future

미래 날짜

@FutureOrPresent

오늘이거나 미래 날짜

@AssertTrue / False

별도 Logic 적용
재사용이 불가능하다.

해당 로직이 필요한 다른 DTO 클래스가 있다면,
거기에도 똑같이 작성해주어야 하는 단점이 있음.

간단 사용 예시

@AssertTrue(message = "yyyyMM의 형식에 맞지 않습니다.") // true가 반환되면 통과 . 메서드 이름이 is로 시작해야됨
public boolean isReqYearMonthValidation() {
    try {
        LocalDate localDate = LocalDate.parse(this.reqYearMonth + "01", DateTimeFormatter.ofPattern("yyyyMMdd"));
    } catch (Exception e) {
        return false;
    }

    return true;
}

@Pattern

정규 표현식(pattern)에 맞아야 함을 나타냄.

@Min

숫자 값이 주어진 최솟값(min) 이상이어야 함을 나타냄.

@Max

숫자 값이 주어진 최댓값(max) 이하여야 함을 나타냄.

@Email

문자열이 유효한 이메일 주소 형식이어야 함을 나타냄.

@Valid

해당 Object Validation 실행

JSR-380 ( Bean Validation 2.0 ~ 3.0 ) 스펙에 정의된 표준 어노테이션이다.
( Bean Validation 1.0 은 JSR-303 )

이 어노테이션은
객체의 필드나 매개변수에 부착될 수 있으며, 해당 객체의 검증을 수행한다.

컨트롤러의 매개변수에 사용하여
요청 데이터의 유효성을 검증하고
검증 결과에 따라 적절한 처리를 수행하는데 활용되거나,

DTO에 사용하여
객체의 유효성을 검증하는 용도로 사용한다.


@Valid 어노테이션은
검증을 수행하기 위해 Validator 인터페이스를 구현한 검증기(validator)를 사용한다.

Spring Boot에서는 주로 Hibernate Validator를 검증기로 사용한다.

Hibernate Validator는
Bean Validation 스펙의 구현체로, 다양한 검증 어노테이션을 지원하고,
커스텀 검증 로직을 작성할 수 있다.


사용자 정의 검증 어노테이션 : ConstraintValidator

사용자 정의 검증 어노테이션은

구현되어있지 않은 필요한 검증을 작성할 경우,

그리고

@AssertTrue / @AssertFalse 어노테이션을 이용한 검증 메서드를

여러 파일에서 사용해야 할 경우에

메서드를 사용자 정의 어노테이션으로 만들어서 사용하면

유용하게 사용될 수 있다.

assertTrue/False로 구현한 메서드는 재사용이 불가능하기 때문이다.


예시 케이스

년도만 입력받아야 하는 경우를 검증하기위한 어노테이션 생성

검증 형태 : yyyyMM


생성할 파일 목록

- YearMonth.java
- YearMonthValidator.java

어노테이션 정의

우선 , 어노테이션 파일을 생성한 후

다른 어노테이션에서 필요한 설정값을 가져온다.

파일 생성


설정값 가져오기

아무 검증 어노테이션 파일을 열어서,

@Constraint , @Target , @Retention 어노테이션과 ,

message() , groups() , payload() 메서드를 복사해서 가져온다.

가져온 후

message() 메서드의 default 값으로
검증 실패시 보여줄 문구를 입력해주면 좋다.

그리고 이번 실습에서 검증 형태를 지정해주기 위해
pattern() 메서드를 추가로 만들어준다.
이 때 , default로 검증 형태를 입력해두면 좋다.

@Constraint(validatedBy = { }) 
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface YearMonth {
    String message() default "{jakarta.validation.constraints.Email.message}";

    Class<?>[] groups() default { };

    Class<? extends Payload>[] payload() default { };
    
    // 문자열 형태 검열을 위해 추가해준 메서드
    String pattern() default "yyyyMM";
}

검증 파일 작성

이 검증 파일은,
어노테이션 파일의 @Constraint에 입력해줄 파일이다.

파일 이름은 YearMonthValidator라고 작성해주었고,
파일 형태는 Class 파일이다.

파일 생성 후
ConstraintValidator<YearMonth, String> 인터페이스를 상속받아준다.

이 때,
YearMonth는 검증값을 입력 받을 어노테이션 이름이고
String은 입력받을 값의 자료형이다.

인터페이스를 상속받고
initialize() 메서드와 , isValid() 메서드를 Override 해서 내용을 작성해준다.

initialize() 메서드에는 초기화가 필요한 경우,
어노테이션에서 메서드를 불러와서 사용할 수 있다.

isValid() 메서드에서는 검증할 내용을 작성해주면 된다.

public class YearMonthValidator implements ConstraintValidator<YearMonth, String> {

    private String pattern;

    @Override
    public void initialize(YearMonth constraintAnnotation) {
        this.pattern = constraintAnnotation.pattern();
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext context) {
        // this.pattern = yyyyMM
        // value = 검증할 사용자 입력 값

        try {
            LocalDate localDate = LocalDate.parse(value + "01", DateTimeFormatter.ofPattern(this.pattern + "dd"));
        } catch (Exception e) {
            return false;
        }

        return true;
    }
}

Validator 파일을 작성 완료했다면,

다시 YearMonth 어노테이션 작성 파일로 돌아와서

상단의 @Constraint 메서드에 작성한 Validator 파일이름을 입력해준다.

@Constraint(validatedBy = {YearMonthValidator.class}) 
...



어노테이션 사용은 다른 어노테이션 사용하듯이 하면 됨!

public Class User {
    @YearMonth
    private String reqYearMonth;
}




간단 실습

의존성 추가

implementation 'org.springframework.boot:spring-boot-starter-validation'

1. Controller에서 Dto 검증

DTO 클래스에서 검증할 필드에 어노테이션 부착

@Getter
@Setter
@ToString
public class Users {

    private String name;

    @Min(value = 0)
    @Max(value = 90, message = "나이 제한 사항 : 90세 이하만 가능")
    private int age;
    
    @Email 
    private String email;

	private String phoneNumber;
    
    @Valid
    List<Car> cars;

}

@Getter
@Setter
@ToString
public class Car {
	
    @NotBlank
    private String name;

}

Controller에 검증 지시

Controller에서
RequestBody 값을 입력 받을 메서드에
@Valid 어노테이션을 붙여주어야 한다.

@RestController
@RequestMapping("/validation")
public class ValidationController {

    @PostMapping("/user")
    public ResponseEntity user(@Valid @RequestBody Users user) {
        System.out.println(user);

        return ResponseEntity
                .status(HttpStatus.OK)
                .body(user);
    }
}

실행 테스트

email 형식 엉망으로 보내보기


spring boot log message

2023-06-06T15:15:17.567+09:00  
WARN 15708 --- [nio-8888-exec-1] 
.w.s.m.s.DefaultHandlerExceptionResolver : 
Resolved [org.springframework.web.bind.MethodArgumentNotValidException: 
Validation failed for argument [0] in 
public org.springframework.http.ResponseEntity com.example.partthree.controller.ValidationController.user(com.example.partthree.validation.dto.Users): 
[Field error in object 'users' on field 'email': rejected value [asdasd]; 
codes [Email.users.email,Email.email,Email.java.lang.String,Email]; 
arguments [org.springframework.context.support.DefaultMessageSourceResolvable:
codes [users.email,email]; 
arguments []; 
default message [email],[Ljakarta.validation.constraints.Pattern$Flag;@486472a5,.*]; 
default message [올바른 형식의 이메일 주소여야 합니다]] ]

email 형식 지켜서 보내보기

예외 처리 방법 외에, BindingResult 로 에러 출력 해보기

@Valid 어노테이션을 사용한 메서드에서
BindingResult 를 사용하면
결과값이 BindingResult로 들어온다.

메서드에 BindingResult 추가

@PostMapping("/user")
public ResponseEntity user(@Valid @RequestBody Users user, BindingResult bindingResult) {

    if (bindingResult.hasErrors()) {
        StringBuilder sb = new StringBuilder();
        bindingResult.getAllErrors().forEach(objectError -> {
            FieldError field = (FieldError) objectError;
            String message = objectError.getDefaultMessage();

            sb.append("\nfield : ").append(field.getField())
              .append("\nmessage : ").append(message);
        });

        return ResponseEntity
                .status(HttpStatus.BAD_REQUEST)
                .body(sb.toString());
    }

    System.out.println(user);

    return ResponseEntity
            .status(HttpStatus.OK)
            .body(user);
}

에러 발생확인
bindingResult.hasErrors()

발생한 모든 에러에 대한 정보 조회
List<ObjectError> bindingResult.getAllErrors()

FieldError 정보 조회를 위한 형 변환
FieldError field = (FieldError) objectError;

field 이름 정보 조회
field.getField()

오류 메세지 조회
objectError.getDefaultMessage()


실행 결과

spring boot 에러 로그는 출력되지 않음.



2. Controller에서 개별 데이터 검증

String name, int age 등등

Dto클래스를 사용하지 않고 넘어오는 데이터를 검증하는 방법!

Class에 @Validated 어노테이션을 붙여준다.

@RestController
@RequestMapping("/exception")
@Validated
public class ExceptionController {
	...
}

검증할 메서드에 검증 어노테이션을 붙여준다.

    @GetMapping
    public UserExcept get(
            @Size(min = 1)
            @RequestParam String name
            ,
            @NotNull
            @RequestParam Integer age) {
            
            ...
    }

실행 결과

spring boot err log

org.springframework.web.bind.MissingServletRequestParameterException
profile
Hello velog! 
post-custom-banner

0개의 댓글