1. 개요

이 빠른 사용방법(예제)에서는 Spring에서 @Valid@Validated 어노테이션 의 차이점에 중점을 둘 것  입니다.

사용자 입력을 확인하는 것은 대부분의 응용 프로그램에서 일반적인 기능입니다. Java 생태계에서는 이를 지원하기 위해 특별히 Java Standard Bean Validation API사용합니다 . 게다가 이것은 버전 4.0부터 Spring과도 잘 통합됩니다. @Valid@Validated  어노테이션이 표준 Bean API에서 줄기 .

다음 섹션에서 자세히 살펴보겠습니다.

2. @Valid@Validated  어노테이션

Spring에서는 메소드 레벨 유효성 검증을 위해 JSR-303의 @Valid 어노테이션을 사용합니다 . 또한 유효성 검사를 위해 멤버 속성을 표시하는 데에도 이를 사용합니다 . 그러나 이 어노테이션은 그룹 유효성 검사를 지원하지 않습니다.

그룹은 유효성 검사 중에 적용되는 제약 조건을 제한하는 데 도움이 됩니다. 한 가지 특정 사용 사례는 UI 마법사입니다. 여기에서 첫 번째 단계에서 필드의 특정 하위 그룹이 있을 수 있습니다. 다음 단계에서 동일한 Bean에 속하는 다른 그룹이 있을 수 있습니다. 따라서 각 단계에서 이러한 제한된 필드에 제약 조건을 적용해야 하지만 @Valid 는 이를 지원하지 않습니다.

이 경우 그룹 수준 의 경우 이 JSR-303의 @Valid 의 변형인 Spring의 @Validated 를 사용해야 합니다. 이것은 메서드 수준에서 사용됩니다. 그리고 멤버 속성을 표시하기 위해 @Valid 어노테이션을 계속 사용합니다 .

이제 예제를 통해 이러한 어노테이션의 사용법을 살펴보겠습니다.

3. 예

Spring Boot를 사용하여 개발한 간단한 사용자 등록 양식을 생각해 봅시다. 먼저 이름비밀번호 속성 만 사용하겠습니다 .

public class UserAccount {

    @NotNull
    @Size(min = 4, max = 15)
    private String password;

    @NotBlank
    private String name;

    // standard constructors / setters / getters / toString
     
}

다음으로 컨트롤러를 살펴보겠습니다. 여기에서는 사용자 입력의 유효성을 검사하기 위해 @Valid 어노테이션 이 있는 saveBasicInfo 메서드가 있습니다 .

@RequestMapping(value = "/saveBasicInfo", method = RequestMethod.POST)
public String saveBasicInfo(
  @Valid @ModelAttribute("useraccount") UserAccount useraccount, 
  BindingResult result, 
  ModelMap model) {
    if (result.hasErrors()) {
        return "error";
    }
    return "success";
}

이제 이 방법을 테스트해 보겠습니다.

@Test
public void givenSaveBasicInfo_whenCorrectInput_thenSuccess() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfo")
      .accept(MediaType.TEXT_HTML)
      .param("name", "test123")
      .param("password", "pass"))
      .andExpect(view().name("success"))
      .andExpect(status().isOk())
      .andDo(print());
}

테스트가 성공적으로 실행되었는지 확인한 후 이제 기능을 확장해 보겠습니다. 다음 논리적 단계는 대부분의 마법사와 마찬가지로 이를 다단계 등록 양식으로 변환하는 것입니다. 이름암호있는 첫 번째 단계 는 변경되지 않은 상태로 유지됩니다. 두 번째 단계에서는 나이  및 전화번호 와  같은 추가 정보를 가져옵니다 . 따라서 다음 추가 필드로 도메인 개체를 업데이트합니다.

public class UserAccount {
    
    @NotNull
    @Size(min = 4, max = 15)
    private String password;
 
    @NotBlank
    private String name;
 
    @Min(value = 18, message = "Age should not be less than 18")
    private int age;
 
    @NotBlank
    private String phone;
    
    // standard constructors / setters / getters / toString   
    
}

그러나 이번에는 이전 테스트가 실패했음을 알 수 있습니다. 이는 여전히 UI의 그림에 없는 agephone 필드를 전달하지 않기 때문 입니다. 이 동작을 지원하려면 그룹 유효성 검사와 @Validated 어노테이션 이 필요합니다 .

이를 위해 두 개의 개별 그룹을 생성하는 필드를 그룹화해야 합니다. 먼저 두 개의 마커 인터페이스를 만들어야 합니다. 각 그룹 또는 각 단계에 대해 별도의 것입니다. 정확한 구현에 대해서는 그룹 유효성 검사에 대한 기사를 참조할 수 있습니다 . 여기서는 어노테이션의 차이점에 중점을 두겠습니다.

우리는해야합니다 BasicInfo의 첫 번째 단계와에 대한 인터페이스  AdvanceInfo  두 번째 단계입니다. 또한 다음과 같이 이러한 마커 인터페이스를 사용하도록 UserAccount 클래스를 업데이트 합니다.

public class UserAccount {
    
    @NotNull(groups = BasicInfo.class)
    @Size(min = 4, max = 15, groups = BasicInfo.class)
    private String password;
 
    @NotBlank(groups = BasicInfo.class)
    private String name;
 
    @Min(value = 18, message = "Age should not be less than 18", groups = AdvanceInfo.class)
    private int age;
 
    @NotBlank(groups = AdvanceInfo.class)
    private String phone;
    
    // standard constructors / setters / getters / toString   
    
}

또한 이제 @Valid 대신 @Validated 어노테이션 을 사용하도록 컨트롤러를 업데이트합니다 .

@RequestMapping(value = "/saveBasicInfoStep1", method = RequestMethod.POST)
public String saveBasicInfoStep1(
  @Validated(BasicInfo.class) 
  @ModelAttribute("useraccount") UserAccount useraccount, 
  BindingResult result, ModelMap model) {
    if (result.hasErrors()) {
        return "error";
    }
    return "success";
}

이 업데이트의 결과로 이제 테스트가 성공적으로 실행됩니다. 이제 이 새로운 방법도 테스트해 보겠습니다.

@Test
public void givenSaveBasicInfoStep1_whenCorrectInput_thenSuccess() throws Exception {
    this.mockMvc.perform(MockMvcRequestBuilders.post("/saveBasicInfoStep1")
      .accept(MediaType.TEXT_HTML)
      .param("name", "test123")
      .param("password", "pass"))
      .andExpect(view().name("success"))
      .andExpect(status().isOk())
      .andDo(print());
}

이것도 성공적으로 실행됩니다. 따라서 @Validated 의 사용이  그룹 유효성 검사에 필수적인 방법을 알 수 있습니다 .

다음으로 @Valid 가 중첩 속성의 유효성 검사를 트리거하는 데 필수적인 방법을 살펴보겠습니다 .

4. @Valid 어노테이션을 사용하여 중첩 객체 표시하기

@Valid 어노테이션은 특히 중첩 된 속성을 표시하는 데 사용됩니다 . 이렇게 하면 중첩된 개체의 유효성 검사가 트리거됩니다. 예를 들어, 현재 시나리오에서 UserAddress 객체를 생성해  보겠습니다  .

public class UserAddress {

    @NotBlank
    private String countryCode;

    // standard constructors / setters / getters / toString
}

이 중첩 객체의 유효성을 확인하기 위해 @Valid 어노테이션으로 속성을 장식합니다 .

public class UserAccount {
    
    //...
    
    @Valid
    @NotNull(groups = AdvanceInfo.class)
    private UserAddress useraddress;
    
    // standard constructors / setters / getters / toString 
}

5. 장단점

Spring에서 @Valid@Validated 어노테이션 을 사용할 때의 장단점을 살펴보자 .

@Valid 어노테이션은 전체 개체의 유효성을 보장합니다. 중요한 것은 전체 개체 그래프의 유효성 검사를 수행한다는 것입니다. 그러나 이는 부분 검증만 필요한 시나리오에 대한 문제를 생성합니다.

반면에  위의 부분 유효성 검사를 포함하여 그룹 유효성 검사에 @Validated사용할 수 있습니다 .  그러나 이 경우 검증된 엔터티는 모든 그룹 또는 사용 사례에 대한 검증 규칙을 알아야 합니다. 여기서 우리는 우려 사항을 혼합하므로 안티 패턴이 발생할 수 있습니다.

6. 결론

이 빠른 사용방법(예제)에서는 @Valid@Validated Annotations 간의 주요 차이점을 살펴보았습니다 .

결론적으로 모든 기본 유효성 검사를 위해 메서드 호출에서 JSR @Valid 어노테이션을 사용합니다. 반면에 그룹 시퀀스를 포함한 모든 그룹 유효성 검사  의 경우 메서드 호출에서 Spring의 @Validated 어노테이션 을 사용해야 합니다. @Valid  어노테이션은 중첩 된 속성의 유효성 검사를 실행하는 데 필요합니다.

항상 그렇듯이 이 기사에 제공된 코드는 GitHub 에서 사용할 수  있습니다 .

Generic footer banner