프로그래밍 언어/JAVA, SPRING

어노테이션 @Valid와 @Validated

doomole 2023. 9. 11. 18:03
728x90

신규 프로젝트를 수행하면서 @Validated 어노테이션을 접하게 되었다.

if문을 사용하지 않고 간단하게 유효성 검증을 할 수 있는 좋은 기능이었고,

완벽하게 짚고 넘어가기 위해 정리글을 작성하게 되었다.


@Valid

자바 표준 스펙으로, Controller 계층에서 사용이 가능하다.

주로 request body를 검증하는 데 많이 사용된다.

MethodArgumentNotValidException 예외를 발생시킨다.


예제

사용자 정보에 대한 데이터가 들어올 때에 대한 검증예시를 작성해봤다.

이름, 전화번호, 이메일을 body로 전달받을 때 @Valid 어노테이션을 통한 검증이다.

Controller

package com.doomole.stockproject.controller;

import com.doomole.stockproject.dto.ValidDto;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

@RestController
public class TestValidController {

    @RequestMapping(value = "/test/valid", method = RequestMethod.POST)
    public void testValid(@Valid @RequestBody ValidDto validDto) {

    }
}

Dto

package com.doomole.stockproject.dto;
import lombok.Getter;
import javax.validation.constraints.*;

@Getter
public class ValidDto {
    @NotEmpty
    private String name;

    @NotNull
    @Max(value = 11, message = "-를 제외한 11자리를 입력하세요.")
    @Min(value = 11, message = "-를 제외한 11자리를 입력하세요.")
    private int phoneNumber;

    @NotEmpty
    @Email
    private String email;
}

postman을 통해 json데이터 전송 시 각각의 검증에 맞게 아래 에러를 발생시킨다.

name

더보기

{

"codes": [

    "NotEmpty.validDto.name",
    "NotEmpty.name",
    "NotEmpty.java.lang.String",
    "NotEmpty"
],
"arguments": [{
    "codes": [
        "validDto.name",
        "name"
    ],
    "arguments": null,
    "defaultMessage": "name",
    "code": "name"
}],
"defaultMessage": "비어 있을 수 없습니다",
"objectName": "validDto",
"field": "name",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotEmpty"
}

phoneNumber

더보기

{

"codes": [

    "NotEmpty.validDto.email",
    "NotEmpty.email",
    "NotEmpty.java.lang.String",
    "NotEmpty"
],
"arguments": [{
    "codes": [
        "validDto.email",
        "email"
    ],
    "arguments": null,
    "defaultMessage": "email",
    "code": "email"
}],
"defaultMessage": "비어 있을 수 없습니다",
"objectName": "validDto",
"field": "email",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotEmpty"
}

email

더보기
{
"codes": [
    "Min.validDto.phoneNumber",
    "Min.phoneNumber",
    "Min.int",
    "Min"
],
"arguments": [{
    "codes": [
        "validDto.phoneNumber",
        "phoneNumber"
    ],
    "arguments": null,
    "defaultMessage": "phoneNumber",
    "code": "phoneNumber"
},11],
"defaultMessage": "-를 제외한 11자리를 입력하세요.",
"objectName": "validDto",
"field": "phoneNumber",
"rejectedValue": 0,
"bindingFailure": false,
"code": "Min"
},

@Validated

스프링에서 제공하는 어노테이션으로 Controller 이외의 계층에서도 검증이 가능하다.
그룹 유효성 검사를 사용하여 유연성 있게 검증할 수 있다.

ConstraintViolationException 예외를 발생시킨다.


예제

그룹별 유효성 검사를 사용하여 사용자 정보에 대한 데이터가 들어올 때에 대한 검증예시를 작성해봤다.

이름, 전화번호, 이메일, OS, 성별이 있는 DTO를 전달받을 때 각각의 그룹에서 받는 예시에 대해 작성했다.

Controller

package com.doomole.stockproject.controller;

import com.doomole.stockproject.dto.ValidationDto;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestValidationController {
    @RequestMapping(value = "/test/validation01", method = RequestMethod.POST)
    public void testValidation01(@RequestBody @Validated(ValidatedDto.Val01.class) ValidatedDto validationDto) {

    }

    @RequestMapping(value = "/test/validation02", method = RequestMethod.POST)
    public void testValidation02(@RequestBody @Validated(ValidatedDto.Val02.class) ValidatedDto validatedDto) {

    }
}

DTO

package com.doomole.stockproject.dto;

import lombok.Getter;

import javax.validation.constraints.*;

@Getter
public class ValidatedDto {
    public interface Val01 {};

    public interface Val02 {};

    @NotEmpty(groups = {Val01.class, Val02.class})
    private String name;

    @NotEmpty(groups = {Val01.class, Val02.class})
    @Email
    private String email;

    @NotNull(groups = {Val01.class})
    @Max(groups = {Val01.class}, value = 11, message = "-를 제외한 11자리를 입력하세요.")
    @Min(groups = {Val01.class}, value = 11, message = "-를 제외한 11자리를 입력하세요.")
    private int phoneNumber;

    @NotNull(groups = {Val02.class})
    @Max(groups = {Val02.class}, value = 1)
    @Min(groups = {Val02.class}, value = 1)
    private int Gender;

}

Response는 @Valid와 동일하지만 groups를 통해서 어떤 interface에 적용할지를 선택하여 검증을 분기처리 할 수 있다.

다만 모든 검증에 대해 interface를 지정해줘야 한다.(더 효율적인 사용방법이 있다면 댓글 부탁드립니다.)

 

* 더 좋은 사용방법이나 문의사항, 수정할 점은 댓글로 달아주세요.