web-dev-qa-db-ja.com

Spring MVC + Hibernate:データ検証戦略

SpringMVCは一般的にHibernateValidatorおよびJSR-303とうまく統合できることは誰もが知っています。しかし、誰かが言ったように、HibernateValidatorはBeanValidation専用のものです。つまり、より複雑な検証をデータレイヤーにプッシュする必要があります。このような検証の例:ビジネスキーの一意性、レコード内の依存関係(通常、DB設計の問題を指摘するものですが、私たち全員が不完全な世界に住んでいます)。文字列フィールドの長さのような単純な検証でさえ、何らかのDB値によって駆動される可能性があり、HibernateValidatorを使用できなくなります。

だから私の質問は、Spring、Hibernate、またはJSRがそのような複雑な検証を実行するために提供するものはありますか? SpringとHibernateに基づく標準のController-Service-Repositoryセットアップでそのような検証を実行するための確立されたパターンまたはテクノロジーピースはありますか?

PDATE:具体的に説明します。たとえば、AJAX保存リクエストをコントローラーのsaveメソッドに送信するフォームがあります。単純または「複雑」のいずれかの検証エラーが発生した場合は、次のようになります。問題のあるフィールドと関連するエラーを示すjsonを使用してブラウザに戻ります。単純なエラーの場合は、フィールド(存在する場合)とエラーメッセージをBindingResultから抽出できます。 )「複雑な」エラーを提案しますか?saveメソッドと@ExceptionHandlerの間で検証の単一プロセスを分離すると、物事が複雑になるため、例外ハンドラーを使用することは私には良い考えではないようです。アドホック例外(ValidationExceptionなど)を使用します。

public @ResponseBody Result save(@Valid Entity entity, BindingResult errors) {
    Result r = new Result();
    if (errors.hasErrors()) {
        r.setStatus(Result.VALIDATION_ERROR);     
        // ...   
    } else {
        try {
            dao.save(entity);
            r.setStatus(Result.SUCCESS);
        } except (ValidationException e) {
            r.setStatus(Result.VALIDATION_ERROR);
            r.setText(e.getMessage());
        }
    }
    return r;
}

より最適なアプローチを提供できますか?

22
Andrey Balaguta

はい、古き良き確立されたJavaパターン例外スローがあります。
Spring MVCはそれをかなりうまく統合します(コード例については、私の答えの2番目の部分に直接スキップできます)。

「複雑な検証」と呼ばれるものは、実際には例外です。ビジネスキーの単一性エラー、下位層またはDBエラーなどです。


注意:Spring MVCでの検証とは何ですか?

検証はプレゼンテーション層で行う必要があります。基本的には、送信されたフォームフィールドを検証することです。

それらは2種類に分類できます。

1)軽い検証(JSR-303/Hibernate検証を使用):送信されたフィールドに指定された@Size/@Lengthがあり、それが@NotNullまたは@NotEmptyであることを確認します/ @NotBlank@Email形式であることの確認など。

2)重い検証、または複雑な検証は、クロスフィールド検証などのフィールド検証の特定のケースに関するものです。

  • 例1:フォームにはfieldAfieldB、およびfieldCがあります。個別に、各フィールドは空にすることができますが、少なくとも1つは空であってはなりません。
  • 例2:userAgeフィールドの値が18未満の場合、responsibleUserフィールドはnullであってはならず、responsibleUserの年齢は21歳を超えている必要があります。

これらの検証は、 Spring Validatorの実装 または カスタムアノテーション/制約 で実装できます。

これらすべての検証機能に加えて、Springはまったく邪魔にならず、(良くも悪くも)やりたいことが何でもできるという事実に加えて、漠然と関連するものすべてに「検証ハンマー」を使用したくなることがあることを理解しました。エラー処理へ。
そしてそれは機能します:検証のみで、バリデーター/アノテーションで起こりうるすべての問題をチェックします(そして下位層で例外をスローすることはほとんどありません)。あなたはすべての事件について考えたことを祈るので、それは悪いことです。 Java例外を利用しないと、ロジックが単純化され、何かにエラーがあることを確認するのを忘れてミスを犯す可能性を減らすことができます。

したがって、Spring MVCの世界では、検証(つまり、UI検証)を下位層と間違えないでください。 )exceptions、サービス例外またはDB例外(キーユニシティなど)があります。


Spring MVCで例外を便利な方法で処理するにはどうすればよいですか?

一部の人々は、「ああ、私のコントローラーでは、チェックされた可能性のあるすべての例外を1つずつチェックし、それぞれのメッセージエラーについて考える必要がありますか?まさか!」と考えます。私はその一人です。 :-)

ほとんどの場合、すべての例外が拡張する一般的なチェック済み例外クラスを使用するだけです。次に、Spring MVCコントローラーで @ ExceptionHandler と一般的なエラーメッセージを使用して処理します。

コード例:

public class MyAppTechnicalException extends Exception { ... }

そして

@Controller
public class MyController {

    ...

    @RequestMapping(...)
    public void createMyObject(...) throws MyAppTechnicalException {
        ...
        someServiceThanCanThrowMyAppTechnicalException.create(...);
        ...
    }

    ...

    @ExceptionHandler(MyAppTechnicalException.class)
    public String handleMyAppTechnicalException(MyAppTechnicalException e, Model model) {

        // Compute your generic error message/code with e.
        // Or just use a generic error/code, in which case you can remove e from the parameters
        String genericErrorMessage = "Some technical exception has occured blah blah blah" ;

        // There are many other ways to pass an error to the view, but you get the idea
        model.addAttribute("myErrors", genericErrorMessage);

        return "myView";
    }

}

シンプル、迅速、簡単、そしてクリーン!

特定の例外のエラーメッセージを表示する必要がある場合、または変更できないレガシーシステムの設計が不十分なために一般的なトップレベルの例外を設定できない場合は、他の@ExceptionHandlersを追加するだけです。
別のトリック:コードがすっきりするように、複数の例外を処理できます。

@ExceptionHandler({MyException1.class, MyException2.class, ...})
public String yourMethod(Exception e, Model model) {
    ...
}

結論:検証をいつ使用するか?いつ例外を使用するのですか?

  • UIからのエラー=検証=検証機能(JSR-303アノテーション、カスタムアノテーション、Springバリデーター)
  • 下位層からのエラー=例外

「UIからのエラー」とは、「ユーザーがフォームに何か間違ったことを入力した」という意味です。

参照:

41
Jerome Dalbert