SpringBoot(3) Validation 과 Custom Validation, @Annotation 적용 및 실습

Yeppi's 개발 일기·2022년 7월 4일
1

Spring&SpringBoot

목록 보기
11/16

1. Vaildation

1) Vaildation 이란?

프로그래밍에 있어서 가장 중요한 부분

예를 들어 Java에서는 null 값에 접근 시, null pointer exception이 발생한다.

이러한 부분을 방지 하기 위해서 미리 검증을 하는 과정 = Validation





2) Validation 특징

  1. 검증해야 할 값이 많은 경우 코드의 길이가 길어짐

  2. Service Logic과 분리

  3. 흩어져 있는 경우 어디에서 검증을 하는지 알기 어렵다

  4. 재사용의 한계

  5. 검증 Logic이 변경 되는 경우
    참조하는 클래스(해동 Logic 을 사용하는 쪽)에서 Logic이 변경 발생





3) Validation @Annotation

종류기능
@Sizenull 불가능
@NotNullnull, “” 불가능
@NotBlanknull, “”, “ “ 불가능
@Past과거 날짜
@PastOrPresent오늘 or 과거 날짜
@Future미래 날짜
@FutureOrPresent오늘 or 미래 날짜
@Pattern정규식 적용
@Max최대값
@Min최소값
@AssertTrue/False별도 로직 적용
@Valid객체 검증(Validation) 실행




4) 실습

인텔리제이로 실습을 진행했다

📌기본적인 Validation📌

  • gradle dependecies
    https://hibernate.org/validator/

    dependencies {
        implementation 'org.springframework.boot:spring-boot-starter-web'
        implementation 'org.springframework.boot:spring-boot-starter-validation' // 추가
        testImplementation 'org.springframework.boot:spring-boot-starter-test'
    }
  • bean validation 예시
    https://hibernate.org/validator/

  • 예전 방식

    @RestController
    @RequestMapping("/api")
    public class ApiController {
    
        @PostMapping("/user")
        public Object user( @RequestBody User user) {
            System.out.println(user);
    
            // 옛날식 코드  =>  개수가 증가하면 관리 힘듦
            if(user.getAge() >= 90 /*user.getPhoneNumber() == "xxx-xxxx-xxxx"*/) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(user);
            }
            return ResponseEntity.ok(user);
        }
    }

@Vaild 사용해보기

  • User.java@Email 추가
    @Email
    private String email;
  • ApiController.java에 @Valid 추가
    @PostMapping("/user")
    public Object user(@Valid @RequestBody User user) { . . . }
  • JSON 에 잘못된 이메일 형식일 경우
    • 404 에러
    • 콘솔창
  • 올바른 이메일 형식일 경우
    • 콘솔에도 잘 출력
      User{name='이예삐', age=20, email='aaa@naver.com', phoneNumber='01012345678'}



📌정규식 활용📌

  • 휴대폰 번호 정규식을 "^\\d{2,3}-\\d{3,4}-\\d{4}$" 로 설정해서 검증하는 예제
  • User.java 에 @Pattern() 추가
    @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$")
    private String phoneNumber;

  • 올바른 전화번호 형식일 경우
    • 콘솔에도 잘 출력

      User{name='이예삐', age=20, email='aaa@naver.com', phoneNumber='010-1234-5678'}


전화번호 오류 발생 시

  • ApiController.java

    // 에러 메시지 출력
    @PostMapping("/user")
    public ResponseEntity user(@Valid @RequestBody User user, BindingResult bindingResult) {
            // 에러 처리
        if(bindingResult.hasErrors()) {
            StringBuilder sb = new StringBuilder();
            bindingResult.getAllErrors().forEach(objectError -> {
                FieldError field = (FieldError) objectError;
                String message = objectError.getDefaultMessage();
    
                System.out.println("filed : " + field.getField());
                System.out.println(message);
    
                            // JSON body에 친절한 에러 메시지 전달
                sb.append("field : " + field.getField());
                sb.append(("message : " + message));
            });
                    return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(sb.toString());
    
        }
    
            // 실질적인 logic 실행되는 부분
    
        System.out.println(user);
        return ResponseEntity.ok(user);
    
    }

  • User.java 에 @Pattern() 에 message 추가
    @Pattern(regexp = "^\\d{2,3}-\\d{3,4}-\\d{4}$", message = "핸드폰 번호의 양식과 맞지 않습니다. 01x-xxx(x)-xxxx")
    private String phoneNumber;

  • 브라우저에 잘못입력하면?
    콘솔에도 에러 메시지가 출력된다



📌@NotBlank, @Max📌

  • User.java

        @NotBlank
        private String name;
    
        @Max(value = 100)
        private int age;
  • 위 실습처럼
    • 100 이상의 나이를 입력하면 에러 메시지 출력


2. Custom Validation

1) Custom Validation

  • 위에서 설명한 Validation @Annotation 를 사용하고 테스트 작성 시, 꼭 예외 케이스가 나오게 된다.
    이러한 예외 사항들을 검사하기 위해 Custom Validation 를 활용한다.

  • 대표적인 ex. 날짜 형식
    @Past 등을 이용하지 않고, String 형태로 값을 사용

  • 방법 2가지

    1. AssertTrue / False와 같은 method 지정을 통해서 Custom Logic 적용 가능
      ⇒ 재사용이 불가능

    2. ConstraintValidator를 적용하여 Custom Logic 적용 가능
      ⇒ 재사용 가능





2) 실습

📌@AssertTrue📌

  • 재사용이 불가능하지만, 간단한 방식

  • boolean 값을 리턴하는 메서드는 is 를 메서드명 앞에 붙이기

    @AssertTrue(message = "yyyyMM 의 형식에 맞지 않습니다.")
    public boolean isReqYearMonthValidation() {
        System.out.println("assert true call");
    
    }



📌@Constraint📌

날짜 형식 어노테이션 직접 만들기

  • YearMonthValidator.class 에서 사용하는 인터페이스 YearMonth.java

    @Constraint(validatedBy = {YearMonthValidator.class})
    @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";
    }
  • @YearMonth 의 구현

    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) {
            // 패턴에 값이 잘 들어갔는 지 value 값으로 확인
            // yyyyMM01
            try {
                LocalDate localDate = LocalDate.parse(value + "01", DateTimeFormatter.ofPattern(this.pattern));
            } catch (Exception e) {
                return false; // 파싱이 안되면, false
            }
            return true; // 파싱이 잘되면, true
        }
    }
  • 사용하기

       @YearMonth
        private String reqYearMonth; // yyyyMM
  • 브라우저 실행 결과 및 콘솔창 출력 결과

    • fales 일때
      filed : reqYearMonth
      yyyyMM 의 형식에 맞지 않습니다.
    • ture 일때는 잘 동작

👉 여러곳에서 재사용하여 사용가능




ObjectMapper 활용하기

  • car.java

       @NotBlank
        private String name;
    
        @NotBlank
        @JsonProperty("car_number")
        private String carNumber;
    
        @NotBlank
        @JsonProperty("TYPE")
        private String type;
  • user.java

    • @Valid 를 꼭 붙여야함
        @NotBlank
        private String name;
    
        @Max(value = 100)
        private int age;
    
        @Valid // 꼭 붙여야 Car 객체에 있는 어노테이션 동작
        private List<Car> cars;
  • 브라우저에서 실행하면 잘 실행

    {
        "name": "홍길동",
        "age": 10,
        "cars": [
            {
                "name": "K5",
                "car_number": "11가 1111",
                "TYPE": "sedan"
            },
            {
                "name": "Q5",
                "car_number": "22가 1111",
                "TYPE": "SUV"
            }
        ]
    }
  • 만약 "car_number": "" 이렇게 body 데이터가 잘못되면

    콘솔창에도

    filed : cars[0].carNumber
    공백일 수 없습니다
profile
imaginative and free developer. 백엔드 / UX / DATA / 기획에 관심있지만 고양이는 없는 예비 개발자👋

0개의 댓글