본문 바로가기

프로그래밍/Spring

[Spring] @Valid @Validated와 유효성 검사 Annotation

@Valid @Validated란

 @Valid@Validated는 서버 사이드에서 유효성 검사를 하기 위해 자바 및 스프링에서 제공하는 어노테이션이다.

@Valid는 javax.validation.constraints 에서 지원하는 어노테이션으로 valid조건들을 검증하고, 유효성 검사에 부합하지 않는 문제 발생 시MethodArgumentNotValidException 발생시킨다.

 

@Validated는 스프링에서 지원하는 어노테이션 기능으로 @Valid와는 몇가지 차이점이 있다.

 

 먼저, 컨트롤러(핸들러)계층에서만 동작하는 @Valid와는 달리 @Validated는 서비스등 컨트롤러뿐만 아니라 다른 계층에서도 유효성 검사를 할 수 있게끔 해준다.

 

(@Valid는 핸들러 어탭터 처리과정인 ArgumentResolver 과정에서 처리되기 때문에 컨트롤러 계층에서만 동작하고, @Validated는 aop로 동작하기 때문에 다른 계층에서도 동작할 수 있다.)

 

 또 다른 차이점으로 @Validated는 groups 기능을 제공하는 차이점이 있다.

* groups를 간단하게 얘기하자면 유효성 검증 시 분기처리를 통해 경우에 따른 유효성 검증을 거칠 수 있도록 하는 기능인데 잘 사용하지 않는다.

유효성 검사 어노테이션 종류

종류 내용
@Null null만 혀용.
@NotNull null 허용하지 않음.
"", " "는 허용.
@NotEmpty null, ""을 허용하지 않음.
" "는 허용.
@NotBlank null, "", " " 모두 허용하지 않음.
@Email 이메일 형식을 검사. 다만 ""의 경우를 통과.
@Pattern(regexp = ) 정규식 검사.
@Size(min=, max=) 길이를 제한할 때 사용.
@Max(value = ) value 이하의 값을 받을 때 사용.
@Min(value = ) value 이상의 값을 받을 때 사용.
@Positive 값을 양수로 제한.
@PositiveOrZero 값을 양수와 0만 가능하도록 제한.
@Negative 값을 음수로 제한.
@NegativeOrZero 값을 음수와 0만 가능하도록 제한.
@Future 현재보다 미래.
@Past 현재보다 과거.
@AssertFalse false 여부, null은 체크 X.
@AssertTrue true 여부, null은 체크 X.

코드 예제

1. spring boot version 2.3 이상의 경우 spring-boot-starter-web 의존성 내부에 있던 validation이 사라진 관계로 따로 의존성 추가가 필요하다.

 

- gradle 의존성 추가

implementation group: 'org.springframework.boot', name: 'spring-boot-starter-validation', version: '2.5.2'

- maven 의존성 추가

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.5.2</version>
</dependency>

 

2. 유효성 검증을 하고자 하는 클래스의 필드에 annotation을 적용

import lombok.Data;
import org.hibernate.validator.constraints.Range;

import javax.validation.constraints.Max;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Data
public class ItemSaveForm {

    @NotBlank
    private String itemName;

    @NotNull
    @Range(min = 1000, max = 1000000)
    private Integer price;

    @NotNull
    @Max(value = 9999)
    private Integer quantity;
}

 아이템 저장 시 요청을 받는 ItemSaveForm 클래스이다. itemName, price, quantity 필드를 가지고 있으며 각각의 성격에 맞는 유효성 검증 annotation을 적용하였다.

 

만약 itemName 필드에 Null 또는 공백으로 요청이 온다면 서버는 MethodArgumentNotValidException를 반환할 것이다.

 

3. Controller에서 유효성 검사를 적용할 API의 Request객체 앞에 @Valid(@Validated) 어노테이션을 추가한다.

@PostMapping("/add")
    public String addItem(@Validated @ModelAttribute("item") ItemSaveForm form, BindingResult bindingResult, RedirectAttributes redirectAttributes) {

        //특정 필드가 아닌 복합 룰 검증
        if (form.getPrice() != null && form.getQuantity() != null) {
            int resultPrice = form.getPrice() * form.getQuantity();
            if (resultPrice < 10000) {
                bindingResult.reject("totalPriceMin", new Object[]{10000, resultPrice}, null);
            }
        }

        // 검증에 실패하면 다시 입력 폼으로
        if (bindingResult.hasErrors()) {
            log.info("errors={}", bindingResult);
            return "validation/v4/addForm";
        }

        Item item = new Item();
        item.setItemName(form.getItemName());
        item.setPrice(form.getPrice());
        item.setQuantity(form.getQuantity());

        //성공 로직
        Item savedItem = itemRepository.save(item);
        redirectAttributes.addAttribute("itemId", savedItem.getId());
        redirectAttributes.addAttribute("status", true);
        return "redirect:/validation/v4/items/{itemId}";
    }

@Validated로 검증한 객체가 유효하지 않은 객체라면 addItem메소드의 BindingResult파라미터로 에러 객체가 들어온다.

 

* BindingResult가 있으면 @ModelAttribute에 데이터 바인딩 시 오류가 발생해도 오류 정보를 FieldError 개체를 BindingResult가 담은 뒤 컨트롤러가 호출된다. 여기서 BindingResult 객체의 파라미터는 반드시 @ModelAttribute 어노테이션이 붙은 객체 다음에 위치하여야 한다.

// 검증에 실패하면 다시 입력 폼으로
if (bindingResult.hasErrors()) {
    log.info("errors={}", bindingResult);
    return "validation/v4/addForm";
}

이후 bindingResult.hasErrors() 조건문을 통해 사용자가 원하고자 하는 함수나 리턴값을 반환하여 에러를 처리하면 된다.

'프로그래밍 > Spring' 카테고리의 다른 글

[Spring] AOP  (0) 2023.03.21
[Spring] 예외처리 (@ExceptionHandler, @ControllerAdvice)  (0) 2022.04.17
[Spring] (SpringBoot) SQL 로그 남기기  (0) 2022.03.29
[Spring] log4j  (0) 2022.03.28
[Spring] @Transactional  (0) 2022.03.28