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

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

Spring&SpringBoot

목록 보기
11/16
post-custom-banner

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 / 기획에 관심있지만 고양이는 없는 예비 개발자👋
post-custom-banner

0개의 댓글