データ]

  • リンク:/tag/spring-data-rest/[春のデータREST]


1概要

この記事では、Spring Data REST Validatorの基本的な紹介を扱います。

もしあなたが最初にSpring Data RESTの基礎を学ぶ必要があるなら、間違いなく

この記事

にアクセスして基礎を磨いてください。

簡単に言うと、Spring Data RESTでは、REST APIを通じてデータベースに新しいエントリを追加するだけですが、もちろん実際にデータを永続化する前にデータが有効であることを確認する必要もあります。

この記事はリンクに続けてあります:/spring-data-rest-intro[既存の記事]そして我々はそこで設定した既存のプロジェクトを再利用します。


2

Validators


を使う

Spring 3以降、このフレームワークは

Validator

インタフェースを備えています。これはオブジェクトの検証に使用できます。

** 2.1. 動機

**

前回の記事では、

name



email

という2つのプロパティを持つエンティティを定義しました。

したがって、新しいリソースを作成するには、単に実行する必要があります。

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "name" : "Test", "email" : "[email protected]" }'
  http://localhost:8080/users

このPOSTリクエストは提供されたJSONオブジェクトを私たちのデータベースに保存し、オペレーションは戻ります:

{
  "name" : "Test",
  "email" : "[email protected]",
  "__links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

我々が有効なデータを提供したので、良い結果が期待された。しかし、プロパティ

name

を削除するか、単に値を空の

String

に設定するとどうなりますか?

最初のシナリオをテストするには、空の文字列をプロパティ

name

の値として設定する前から、変更したコマンドを実行します。

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "name" : "", "email" : "Baggins" }' http://localhost:8080/users

そのコマンドにより、次のような応答が得られます。

{
  "name" : "",
  "email" : "Baggins",
  "__links" : {
    "self" : {
        "href" : "http://localhost:8080/users/1"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/1"
    }
  }
}

2番目のシナリオでは、リクエストからプロパティ

name

を削除します。

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "email" : "Baggins" }' http://localhost:8080/users

そのコマンドに対して、次のような応答が得られます。

{
  "name" : null,
  "email" : "Baggins",
  "__links" : {
    "self" : {
        "href" : "http://localhost:8080/users/2"
    },
    "websiteUser" : {
        "href" : "http://localhost:8080/users/2"
    }
  }
}

ご覧のとおり、どちらの要求もOKで、201のステータスコードとAPIを使用してオブジェクトにリンクしていることを確認できます




データベースに部分的なデータを挿入するのを避けたいので、この動作は許容できません。


2.2. Spring Data RESTイベント

Spring Data REST APIを呼び出すたびに、Spring Data RESTエクスポーターは以下にリストされているさまざまなイベントを生成します。


  • BeforeCreateEvent


  • AfterCreateEvent


  • BeforeSaveEvent


  • AfterSaveEvent


  • BeforeLinkSaveEvent


  • AfterLinkSaveEvent


  • BeforeDeleteEvent


  • AfterDeleteEvent

すべてのイベントは同様の方法で処理されるため、新しいオブジェクトがデータベースに保存される前に生成される

beforeCreateEvent

の処理方法についてのみ説明します。


2.3.

Validator


を定義する

独自のバリデータを作成するには、

supports

および

validate

メソッドを使って

org.springframework.validation.Validator

インターフェースを実装する必要があります。


Supports

はバリデータが提供されたリクエストをサポートしているかどうかをチェックし、

validate

メソッドはリクエストで提供されたデータを検証します。


WebsiteUserValidator

クラスを定義しましょう。

public class WebsiteUserValidator implements Validator {

    @Override
    public boolean supports(Class<?> clazz) {
        return WebsiteUser.class.equals(clazz);
    }

    @Override
    public void validate(Object obj, Errors errors) {
        WebsiteUser user = (WebsiteUser) obj;
        if (checkInputString(user.getName())) {
            errors.rejectValue("name", "name.empty");
        }

        if (checkInputString(user.getEmail())) {
            errors.rejectValue("email", "email.empty");
        }
    }

    private boolean checkInputString(String input) {
        return (input == null || input.trim().length() == 0);
    }
}


Errors

オブジェクトは、

validate

メソッドで提供されるすべてのエラーを含むように設計された特別なクラスです。この記事の後半では、

Errors

オブジェクトに含まれる提供されたメッセージを使用する方法を説明します。新しいエラーメッセージを追加するには、

errors.rejectValue(nameOfField、errorMessage)

を呼び出す必要があります。

バリデータを定義したら、リクエストを受け付けた後に生成される特定のイベントにマッピングする必要があります。

たとえば、私たちの場合、データベースに新しいオブジェクトを挿入したいので、

beforeCreateEvent

が生成されます。しかし、リクエスト内のオブジェクトを検証したいので、まずバリデータを定義する必要があります。

これには3つの方法があります。

  • 名前で

    Component

    アノテーションを追加する



beforeCreateWebsiteUserValidator

”。 Spring Bootはキャッチしたいイベントを決定する接頭辞

beforeCreate

を認識し、また

Component

nameから

WebsiteUser

クラスを認識します。

@Component("beforeCreateWebsiteUserValidator")
public class WebsiteUserValidator implements Validator {
    ...
}


  • @ Bean

    アノテーションを使用してアプリケーションコンテキストに

    Bean

    を作成します。

@Bean
public WebsiteUserValidator beforeCreateWebsiteUserValidator() {
    return new WebsiteUserValidator();
}

  • 手動登録:

@SpringBootApplication
public class SpringDataRestApplication
  extends RepositoryRestConfigurerAdapter {
    public static void main(String[]args) {
        SpringApplication.run(SpringDataRestApplication.class, args);
    }

    @Override
    public void configureValidatingRepositoryEventListener(
      ValidatingRepositoryEventListener v) {
        v.addValidator("beforeCreate", new WebsiteUserValidator());
    }
}

  • ** この場合は、に注釈は必要ありません。


WebsiteUserValidator

クラス。


2.4. イベント発見のバグ

現時点では、https://jira.spring.io/browse/DATAREST-524[バグデータがSpring Data RESTに存在します] – これはイベントの発見に影響します。


beforeCreate

イベントを生成するPOSTリクエストを呼び出した場合、このバグのためにイベントが検出されないため、アプリケーションはバリデータを呼び出しません。

この問題に対する簡単な回避策は、Spring Data RESTの

ValidatingRepositoryEventListener

クラスにすべてのイベントを挿入することです。

@Configuration
public class ValidatorEventRegister implements InitializingBean {

    @Autowired
    ValidatingRepositoryEventListener validatingRepositoryEventListener;

    @Autowired
    private Map<String, Validator> validators;

    @Override
    public void afterPropertiesSet() throws Exception {
        List<String> events = Arrays.asList("beforeCreate");
        for (Map.Entry<String, Validator> entry : validators.entrySet()) {
            events.stream()
              .filter(p -> entry.getKey().startsWith(p))
              .findFirst()
              .ifPresent(
                p -> validatingRepositoryEventListener
               .addValidator(p, entry.getValue()));
        }
    }
}


3テスト中

  • 2.1節** では、バリデータがなくてもnameプロパティを持たないオブジェクトをデータベースに追加できることを示しました。これはデータの整合性をチェックしないため、望ましい動作ではありません。


name

プロパティを使わずにバリデータを指定して同じオブジェクトを追加したい場合、このエラーが発生します。

curl -i -X POST -H "Content-Type:application/json" -d
  '{ "email" : "[email protected]" }' http://localhost:8080/users

{
   "timestamp":1472510818701,
   "status":406,
   "error":"Not Acceptable",
   "exception":"org.springframework.data.rest.core.
    RepositoryConstraintViolationException",
   "message":"Validation failed",
   "path":"/users"
}

ご覧のとおり、リクエストから欠落データが検出され、オブジェクトはデータベースに保存されませんでした。私たちの要求は500のHTTPコードと内部エラーのメッセージで返されました。

エラーメッセージは、私たちの要求の問題については何も言いません。

もっと情報を提供したいのであれば、レスポンスオブジェクトを修正する必要があります。

/Springでの例外処理[Springでの例外処理]のリンクで、フレームワークによって生成された例外を処理する方法を示したので、この時点では間違いなく優れた記事です。

私たちのアプリケーションは

RepositoryConstraintViolationException

例外を生成するので、応答メッセージを修正するこの特定の例外のためのハンドラを作成します。

これが私たちの

__RestResponseEntityExceptionHandle

__rクラスです。

@ControllerAdvice
public class RestResponseEntityExceptionHandler extends
  ResponseEntityExceptionHandler {

    @ExceptionHandler({ RepositoryConstraintViolationException.class })
    public ResponseEntity<Object> handleAccessDeniedException(
      Exception ex, WebRequest request) {
          RepositoryConstraintViolationException nevEx =
            (RepositoryConstraintViolationException) ex;

          String errors = nevEx.getErrors().getAllErrors().stream()
            .map(p -> p.toString()).collect(Collectors.joining("\n"));

          return new ResponseEntity<Object>(errors, new HttpHeaders(),
            HttpStatus.PARTIAL__CONTENT);
    }
}

このカスタムハンドラでは、私たちのreturnオブジェクトはすべての検出されたエラーに関する情報を持ちます。


4結論

この記事では、バリデーターがデータ挿入のための追加のセキュリティー層を提供するすべてのSpring Data REST APIに不可欠であることを示しました。

また、アノテーションを使用して新しいバリデータを作成するのがいかに簡単かを説明しました。

いつものように、このアプリケーションのコードはhttps://github.com/eugenp/tutorials/tree/master/spring-data-rest[GitHubプロジェクト]にあります。