Spring Bootデータ検証の実装とエラーハンドリングの実践

Spring Bootアプリケーションでリクエストデータの妥当性を検証する際、@Valid@Validatedの2つの主要アノテーションが使用されます。両者は共通点がありますが、使用シナリオや実装メカニズムに明確な違いがあります。

アノテーションの核心的差異

  • パッケージ位置: @Validjavax.validationパッケージに、@Validatedorg.springframework.validationパッケージに存在します
  • 適用対象: @Validはフィールド・メソッド戻り値・パラメータなど幅広い対象に適用可能ですが、@Validatedはクラスレベルまたはメソッドパラメータに限定されます
  • 検証実装: @ValidはJSR-380標準規格のHibernate Validatorを基盤とし、@ValidatedはSpring独自の検証ラッパーを経由して実装されます
  • 分組機能: @Validatedは検証グループの指定が可能で、リクエストコンテキストに応じた動的検証が実現できます

主要検証アノテーション一覧

アノテーション機能説明
@Emailメールアドレス形式の妥当性検証
@Size文字列・コレクションの長さ制約
@NotBlank空白文字を含まない文字列の検証
@Pattern正規表現による文字列マッチング
@Min/@Max数値の上限・下限チェック
@DecimalMin小数点付き数値の下限チェック
@Future未来日付の検証

依存関係設定

Spring Bootプロジェクトではspring-boot-starter-validationを追加することで、Bean Validation APIとHibernate Validatorが自動的に設定されます。

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

実装例: ユーザー登録検証

検証グループを定義したDTOクラスを作成します。各フィールドに適切なアノテーションを付与し、グループごとに検証ルールを分離します。

@Data
public class UserRegistrationRequest {
    @Email(regexp = "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", 
           groups = RegistrationValidation.class)
    private String emailAddress;
    
    @Length(min = 2, max = 10, 
            groups = {RegistrationValidation.class, AuthValidation.class})
    private String userName;
    
    @Length(min = 8, groups = RegistrationValidation.class)
    private String userPassword;
    
    @Length(min = 6, max = 6, groups = RegistrationValidation.class)
    private String verificationCode;
}

コントローラーで検証を実行する際、@Validatedに検証グループを指定します。

@RestController
public class UserController {
    @PostMapping("/register")
    public ResponseEntity<?> register(
        @RequestBody @Validated(RegistrationValidation.class) 
        UserRegistrationRequest request
    ) {
        // キャッシュからの検証コード確認処理
        Cache<String> codeCache = verificationCache.get(request.getEmailAddress());
        if (codeCache == null || codeCache.isExpired() || 
            !codeCache.getValue().equals(request.getVerificationCode())) {
            return ResponseEntity.badRequest().body("無効な認証コードです");
        }
        // ユーザー登録ロジック実行
        return userService.register(request);
    }
}

エラーハンドリング戦略

検証エラーを一括で処理するためのグローバル例外ハンドラーを実装します。エラーメッセージをJSON形式で返却する標準的な実装例です。

@RestControllerAdvice
public class ValidationExceptionHandler {
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ResponseEntity<Map<String, String>> handleValidationErrors(
        MethodArgumentNotValidException ex
    ) {
        Map<String, String> errors = ex.getBindingResult()
            .getFieldErrors()
            .stream()
            .collect(Collectors.toMap(
                FieldError::getField, 
                FieldError::getDefaultMessage
            ));
        return ResponseEntity.badRequest().body(errors);
    }
}

メソッドレベルでBindingResultパラメータを直接受け取る方法もあります。この場合、検証結果を手動で処理する必要があります。

@PostMapping("/login")
public ResponseEntity<?> login(
    @RequestBody @Valid AuthRequest request,
    BindingResult result
) {
    if (result.hasErrors()) {
        // エラー処理ロジック
    }
    // 正常処理
}

タグ: spring-boot Validation hibernate-validator bean-validation spring-mvc

7月15日 17:55 投稿