오답노트

[Spring] Validation 본문

Java/Spring

[Spring] Validation

권멋져 2023. 7. 13. 17:09

Validation

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

2. 구현에 따라서 달라 질 수 있지만 Service Logic과 분리가 필요하다.

3. 흩어져 있는 경우 어디에서 검증을 하는지 알기 어려우며, 재사용의 한계가 있다.

4. 구현에 따라 달라 질 수 있지만, 검증 Logic이 변경되는 경우 테스트 코드 등 참조하는 클래스에서 Logic이 변경되어야 하는 부분이 발생 할 수 있다.

 

사용법

import com.example.validation.dto.User;
import jakarta.validation.Valid;
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.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class ApiController {

    @PostMapping("/user")
    public Object user(@Valid @RequestBody User user, BindingResult bindingResult){

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

                System.out.println("feild : " + field.getField());
                System.out.println("msg : " + msg);

                sb.append("feild : " + field.getField());
                sb.append("msg : " + msg);

            });

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


        return user;

    }

}

@Valid Annotation을 이용해 어떤 값을 검증할지 명시한다.

 

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;

public class User {
    @NotBlank
    private String name;
    @Max(value = 90)
    private int age;
    @Email
    private String email;

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPhoneNumber() {
        return phoneNumber;
    }

    public void setPhoneNumber(String phoneNumber) {
        this.phoneNumber = phoneNumber;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", email='" + email + '\'' +
                ", phoneNumber='" + phoneNumber + '\'' +
                '}';
    }
}

변수에 Annotation을 사용하여 어떤 Validation 방식으로 검증할지 명시한다.

각 Annotation 마다 사용법을 검색해서 사용하자. 그리고 각 Annotation은 message 옵션을 통해 출력 메시지를 변경할 수 있다.

 

Custom Validation

기존에 Spring에서 제공하는 Validation Annotation 외에 사용자가 직접 만들어서 사용할 수 있다.

 

import com.example.validation.validator.YearMonthValidator;
import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

@Constraint(validatedBy = {YearMonthValidator.class})
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.ANNOTATION_TYPE,ElementType.CONSTRUCTOR,ElementType.PARAMETER,ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface YearMonth {
    String message() default "yyyyMMdd 의 형식에 맞지 않습니다.";

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

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

    String pattern() default "yyyyMMdd";
}

Annotation으로 사용할 인터페이스를 정의한다. 내용은 Spring에서 제공하던 Annotation을 참고하여 만든다.

pattern 변수는 입력될 패턴을 사용자가 Annotation 사용시 지정할 수 있다. 그리고 default 키워드를 이용해서 사용하지 않을 때, 기본적으로 사용할 패턴을 설정할 수 있다.

import com.example.validation.annotation.YearMonth;
import jakarta.validation.ConstraintValidator;
import jakarta.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) {
        //yyyyMM
        try {
            LocalDate localDate = LocalDate.parse(value + "01", DateTimeFormatter.ofPattern(this.pattern));
        }catch (Exception e){
            return false;
        }

        return true;
    }
}

인터페이스를 상속받아 내용을 구현한 클래스이다. ConstraintValidator를 상속 받아 가상 함수를 구현한다.

initalize에서는 인터페이스의 변수를 받아올 수 있고, isValid에서는 실제로 판별하는 로직을 구현하여 맞으면 true, 위배했다면 false를 return 시킨다.

'Java > Spring' 카테고리의 다른 글

[Spring] Exception과 Validation  (0) 2023.07.14
[Spring] Exception  (0) 2023.07.14
[Spring] AOP  (0) 2023.07.13
[Spirng] IoC, DI  (0) 2023.07.13
[Spring Boot] Object Mapper  (0) 2023.07.13