1. 概要

このクイックチュートリアルでは、Springの@Validアノテーションと@Validatedアノテーションの違いに焦点を当てます。

ユーザーの入力を検証することは、ほとんどのアプリケーションに共通の機能です。 Javaエコシステムでは、特に Java Standard Bean Validation API を使用してこれをサポートします。これは、バージョン4.0以降のSpringと十分に統合されています。 @Validおよび@Validatedアノテーションは、このStandard BeanAPIに由来します。

次のセクションでは、それらについて詳しく説明します。

2. @Validおよび@Validatedアノテーション

Springでは、メソッドレベルの検証にJSR-303の@Validアノテーションを使用します。 検証用のメンバー属性をマークするためにも使用します。 ただし、このアノテーションはグループ検証をサポートしていません。

グループは、検証中に適用される制約を制限するのに役立ちます。 特定のユースケースの1つは、UIウィザードです。 最初のステップでは、フィールドの特定のサブグループがある場合があります。 次のステップでは、同じBeanに属する別のグループが存在する可能性があります。 したがって、各ステップでこれらの制限されたフィールドに制約を適用する必要がありますが、@Validはこれをサポートしていません。

この場合、グループレベルのは、JSR-303の @ValidのバリアントであるSpringの@Validated、を使用する必要があります。  これはメソッドレベルで使用されます。 メンバー属性のマーキングには、引き続き@Validアノテーションを使用します。

それでは、これらのアノテーションの使用法を例を挙げて見ていきましょう。

3. 例

Spring Bootを使用して開発された簡単なユーザー登録フォームを考えてみましょう。 まず、name属性とpassword属性のみがあります。

public class UserAccount {

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

    @NotBlank
    private String name;

    // standard constructors / setters / getters / toString
     
}

次に、コントローラーを見てみましょう。 ここでは、saveBasicInfoメソッドと@Validアノテーションを使用して、ユーザー入力を検証します。

@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());
}

テストが正常に実行されることを確認した後、機能を拡張します。 次の論理的なステップは、ほとんどのウィザードの場合と同様に、これをマルチステップの登録フォームに変換することです。 namepasswordの最初のステップは変更されていません。 2番目のステップでは、agephoneなどの追加情報を取得します。 次に、ドメインオブジェクトを次の追加フィールドで更新します。

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の画像にまだ表示されていないageおよびphone フィールドを渡していないためです。この動作をサポートするために、グループ検証と@Validatedアノテーションが必要です。

このために、フィールドをグループ化して2つの異なるグループを作成する必要があります。 まず、グループごとまたはステップごとに別々の2つのマーカーインターフェイスを作成する必要があります。 これの正確な実装については、グループ検証に関する記事を参照してください。 ここでは、注釈の違いに焦点を当てましょう。

最初のステップにはBasicInfoインターフェースがあり、2番目のステップには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アノテーションは、オブジェクト全体の検証を保証します。重要なことに、@Validアノテーションはオブジェクトグラフ全体の検証を実行します。 ただし、これにより、部分的な検証のみが必要なシナリオで問題が発生します。

一方、上記の部分的な検証を含め、グループの検証には@Validatedを使用できます。ただし、この場合、検証されたエンティティは、すべてのグループまたはユースケースの検証ルールを知っている必要があります。 ‘で使用されます。 ここでは、アンチパターンを引き起こす可能性のある懸念事項を混ぜ合わせています。

6. 結論

この簡単な記事では、@Validアノテーションと@Validatedアノテーションの主な違いについて説明しました。

結論として、基本的な検証では、メソッド呼び出しでJSR @Validアノテーションを使用します。 一方、グループシーケンスを含むすべてのグループ検証では、メソッド呼び出しでSpringの@Validatedアノテーションを使用する必要があります。 ネストされたプロパティの検証をトリガーするには、@Validアノテーションも必要です。

いつものように、この記事で紹介するコードは、GitHubから入手できます。