[Spring] spring-boot Validation

99winnmin·2022년 7월 11일
0

Spring

목록 보기
3/17

Spring Boot Validation

Validation이란 프로그래밍에 있어서 가장 필요한 부분으로 특히 java에서는 null 값에 대해서 접근하려고 할 때 NPE가 발생함으로, 이러한 부분을 방지하기 위해서 미리 검증을 하는 과정을 Validation이라고 함

제일 먼저 build.gradle에 다음을 추가함

    implementation 'org.springframework.boot:spring-boot-starter-validation'
  1. 검증해야 할 값이 많은 경우 코드의 길이가 길어진다.
  2. 구현에 따라서 달라질 수 있지만 Service Logic과의 분리가 필요하다.
  3. 흩어져 있는 경우 어디에서 검증을 하는지 알기 어려우며, 재사용의 한계가 있다.
  4. 구현에 따라 달라질 수 있지만, 검증 Logic이 변경되는 경우 test 코드등 참조하는 클래스에서 Logic이 변경되어야 하는 부분이 발생할 수 있다.

따라서 spring boot에는 일관적인 validator가 존재하고 이는 annotation기반으로 제공됨

  • annotaion 사용 예제, 클래스의 변수에서 붙여준다.
	@NotBlank
    private String name;

    @Min(value = 0, message = "사람의 나이는 양수입니다.")
    private int age;

    @Email
    private String email;

    @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$")
    private String phoneNumber;

유효성 검사를 해보는 코드이다. 그러나 해당 방법은 코드에서 예외처리하므로 logic관련 코드와 같이 있기 때문에 추천되는 방법은 아니다.

  • ValidationApiController.java
package com.example.spring.controller;

import com.example.spring.dto.ValidationUser;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;

@RestController
@RequestMapping("/api")
public class ValidApiController {
    // @Valid을 붙여서 유효성 검사하겠다!
    // BindingResult는 코드에서 직접 예외처리하는 방법!
    @PostMapping("/validation/user")
    public ResponseEntity validationUser(@Valid @RequestBody ValidationUser validationUser,
                                                 BindingResult bindingResult){
        // 유효성 검사에서 에러났을 때 해당 에러 출력하기
        if(bindingResult.hasErrors()){
            StringBuilder sb = new StringBuilder();
            bindingResult.getAllErrors().forEach(objectError -> {
                FieldError field = (FieldError) objectError;
                String message = objectError.getDefaultMessage();

                // 어디서 에러 났는지 key값 알아내기
                System.out.println("field : " + field.getField());
                // 유효성 에러 메세지 출력
               System.out.println(message);
               sb.append("field : "+field.getField());
               sb.append("message : "+message);

            });

            return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(sb.toString());
        }
        System.out.println(validationUser);

        // 코드에서 예외처리하고 Logic 관련 코드가 실행되게함

       return ResponseEntity.ok(validationUser);
    }
}

Custom Validation

  1. AssertTrue/False 와 같은 method 지정을 통해서 Custom Logic 적용 가능
 	// 그러나 한 클래스에 이렇게 만든다면 이런 형식이 필요한 곳에 재사용이 안됨됨
   @AssertTrue(message = "yyyyMM의 형식에 맞지 않습니다.")
    public boolean isReqYearMonthValidation(){
        System.out.println("assert True call");
        try{
            LocalDate localDate = LocalDate.parse(this.reqYearMonth+"01", DateTimeFormatter.ofPattern("yyyyMMdd"));
        }catch (Exception e){
            return false;
        }
        return true;
    }
  1. ConstraintValidatior를 적용하여 재사용이 가능한 Custom Logic 적용 가능
  • Custom annotation 만들기
package com.example.spring.annotation;

import com.example.spring.validator.YearMonthValidator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Constraint(validatedBy = {YearMonthValidator.class}) // YearMonthValidator 객체를 통해 검사가 이뤄짐
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface YearMonth {
    String message() default "yyyyMM 형식에 맞지 않습니다.";

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

    Class<? extends Payload>[] payload() default { };

    String pattern() default "yyyyMMdd";
}
  • annotation에서 사용할 validate 객체
package com.example.spring.validator;

import com.example.spring.annotation.YearMonth;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

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) {
        // yyyyMMdd 체크
        try{
            // LocalDate 이기 때문에 "01"을 붙여야만 MM을 검색하게됨(default로 01일 이라고 붙임)
            LocalDate localDate = LocalDate.parse(value+"01", DateTimeFormatter.ofPattern(this.pattern));
        }catch (Exception e){
            return false;
        }

        return true;
    }
}
  • Custom한 annotation 사용
	@YearMonth(pattern = "yyyyMMdd")
    private String reqYearMonth;

사용과정이 조금 복잡하긴 하지만 재사용성이 높은 validator를 만드는 것이 좋다.

출처 : 한 번에 끝내는 Java/Spring 웹 개발 마스터 초격차 패키지 Online.

profile
功在不舍

0개의 댓글