유효성 검사

가언·2024년 8월 5일

유효성 검사

  • 검증(증명) vs 검사/검정(확인)
  • 백엔드 세상의 조건에 부합하는지 확인
    ex. 비밀번호 규칙

추가 질문

  • (1) 이미 가입된 id(중복 검사) 확인은 유효성 검사일까?
    • 맞다: 잘못된 값이라면 비지니스 로직을 돌리는데 의미가 없다. 앞단에서 유효성검사로 걸러줘야 한다.
    • 아니다: 올바른 값인 것은 맞지만 조건에 맞지 않은 것 뿐, 이를 처리하는 것은 또 다른 하나의 로직이라고 생각한다.
  • (2) 검사는 어디서? 처리는 어디서?
    • 백엔드 세상 조건에 부합하는지 확인해야하기 때문에
    • 컨트롤러에서 검사를 해서 들여보낸다 백엔드 들어가기 직전에!
    • 검사에서 걸렸어 처리좀 해줘!: 처리는 서비스에서 시킴
    • 서비스가 어떤 에러에 걸렸는지 알려줌
    • 컨트롤러가 받아서 클라이언트에게 알려줌
    • 유효성 검사를 예외처리로 풀어내는 것: 어떤 필드가 문제인지 예외처리로 필터링하는 것 (예외 처리를 일종)
    • 즉, controller가 exception을 던지고, service가 받아서 처리한다.
      (정답은 아니고 보통 처리하는 방법)

@Valid( vs @Validated)

1) 어느 dependency 소속일까?: spring boot starter validation
2) 어떤 어노테이션과 같이 쓸까?

  • @Email : 이메일 형식 검사
  • @Size: 값이 min과 max사이에 속해야함 @Size(max = 10, min = 1)
  • @NotBlank: 값이 비어있으면 X
  • @NotNull: Null값이 아니어야 한다.
  • @PositiveOrZero : 0이거나 양수인 값이다.
  • @Future: 날짜 필드가 미래여야 함을 지정
  • @FutureOrPresent: Now 거나 미래의 날짜, 시간이어야 한다.
  • @Max(9999) Min : 최대 9999까지 허용 (최소도 마찬가지)
  • @Pattern: 비밀번호 유효성 검사에 주로 사용

3) 어노테이션들은 어디에다 붙여야 할까?

  • @Valid: Controller/@RequestBody @Valid DTO
  • @NotNull, @Pattern...: DTO field 위

@Validation

  • @Valid는 자바 표준 스펙이고 @Validated는 스프링에서 제공하는 어노테이션
  • @Validated를 통해 그룹 유효성 검사나 Controller가 아닌 다른 계층에서 유효성 검증 가능
  • @Valid는 MethodArgumentNotValidException 예외를, @Validated는 ConstraintViolationException 예외를 발생시킨다.
    AOP 기반으로 메소드 요청을 인터셉터하여 처리된다.

Validator: 유틸성 클래스

  • isNumber
  • isAlpha

SME 라이브러리

  • 자바 표준 라이브러리(ex 컬렉션)를 확장하여 좀 더 편리한 기능을 제공하는 라이브러리
  • 대표적인 예시: Apache commons, Google guava
  • implemenation 'com.google.guava:guava'
    ex. Range.closed(1,10), Strings.inNullOrEmpty(문자열)
    Strings.commonPrefix()/Suffix() <-> padStart()/End();
    joiner.on(문장부호).join(배열)
    🌟ex) Splitter.on("&").trimResults().withKeyValueSeparator("=").split("name=gari&age=20")
    => {name=gari, age=20}

User + Validation

  • 1단계. 회원가입할 때 조건!

    • 이름이 NotBlank

    • 비밀번호 최소 8자리 이상

    • 이메일 주소 이메일 형식에 맞게

      만약 위 조건에 부합하지 않은 값을 입력한다면?

      조건에 만족하지 않는 필드를 모두 보여준다.
      그럼 위 내용들을 에러처리하고 싶은 데 어떻게 해야할까?

      방법1: Errors 파라미터에 추가

       @PostMapping("/join")
       public void join(@RequestBody @Valid UserJoinReq userJoinReq, Errors errors){
           //userService.join(userJoinReq);
           for (FieldError fieldError: errors.getFieldErrors()){
               System.out.println("Error field : "+fieldError.getField());
           }
           System.out.println("회원가입 완료");
       }

      결과화면

      방법1-1: BindingResult

    • Spring validator가 검증/검사 하다가 오류가 발생하면, 오류가 난 특정 필드 or 객체를 Errors(BindingResult)에 담음!

    • BindingResult와 Errors중 어느것이 더 구체적일까?
      BindingResult extends Errors
      : Errors를 상속하는 BindingResult가 더 구체적이다.
      => Errors를 상속받았기 때문에 Errors 인터페이스를 통해서 BindingResult의 유효성 검증 에러 관련 멤버에 접근 가능!
      cf. BindingResult는 "데이터 바인딩 에러" + "유효성 검증 에러"
      ex. @ModelAttribute Model model

  • 그래서 유효성 검사 중 에러가 나면 어디에 담기는걸까?
    AbstractBindingResult의 필드에 addError메소드를 통해 저장함

    방법2: 컨트롤러에서 서비스로 에러를 전달해서 처리해보자!

    @PostMapping("/join")
     @ResponseStatus(HttpStatus.BAD_REQUEST)
     public Map<String, String> join(@RequestBody @Valid UserJoinReq userJoinReq, Errors errors){
         //userService.join(userJoinReq);
         Map<String, String> errormessages=userService.handleErrors(errors);
         //Map => new ApiError(errorMessages);
         return errormessages;
     }
    @Component
    public class UserService {
     public Map<String, String > handleErrors(Errors errors){
         Map<String, String> errorMessages=new HashMap<>();
         for (FieldError fieldError: errors.getFieldErrors()){
             String errorField=fieldError.getField();
             String errorMessage=fieldError.getDefaultMessage();
             errorMessages.put(errorField, errorMessage);
         }
         return errorMessages;
     }
    }

    결과화면

    방법3: 에러를 global하게 처리하자

    Controller

     @PostMapping("/signup")
      public ResponseEntity<String> signup(@RequestBody @Valid UserJoinReq userJoinReq) {
          userService.join(userJoinReq); // 유효성 검사를 통과한 경우 처리 로직 호출
          return ResponseEntity.status(HttpStatus.CREATED).body("success"); // 성공 응답 반환
      }

    Global Exception

     @ExceptionHandler
      @ResponseStatus(HttpStatus.BAD_REQUEST)
      public Map<String, String > handleValidationExceptions(MethodArgumentNotValidException errors) {
          Map<String, String> errorMessages=new HashMap<>();
          for (FieldError fieldError: errors.getFieldErrors()){
              String errorField=fieldError.getField();
              String errorMessage=fieldError.getDefaultMessage();
              errorMessages.put(errorField, errorMessage);
          }
          return errorMessages;
      }
profile
@gari_guri

0개의 댓글