はじめに
はじめまして、新卒1年目のTKDSです!
先日、Spring Bootの入力値チェックについて触れる機会があったため、入力値チェックの使い方について調べてました。
今回は、調べた内容と簡単な使いかたについてご紹介したいと思います。
入力値チェック
Spring Bootでは、入力値の検証を行うための便利な機能が提供されています。
これにより、入力データがアプリケーションの要件を満たしているかどうかを確認できます。
入力値チェックはフォームクラスにチェックする内容を示すアノテーションをつけると行えます。
フォームクラスにつけるアノテーションにはさまざまなものがあります。
一部を記載します。
アノテーション | チェック内容 |
---|---|
@NotNull | Nullでないか |
@Max() | 最大値以下か |
@Min | 最小値以上か |
@Pattern | 正規表現に一致するか |
@Size | 要素数が最小以上かつ最大以下であるか |
アノテーションの実践
サンプルアプリを用いて、実際にアノテーションを行ってみます。 検証用にPOSTリクエストをおくるとjsonを返すアプリを定義します。 まだこのサンプルアプリでは入力値チェックは行われていません。
- コントローラー
// 一部抜粋 @PostMapping("todo-list/{id}") public ResponseEntity<Object> post(@PathVariable Integer id, @RequestBody Todo todo,) { List<Todo> todoList = new ArrayList<>(); todo.setId(1L); todo.setUsrName("test001"); todoList.add(todo); return ResponseEntity.status(HttpStatus.OK).body(todoList); }
- レスポンスボディを受け取る兼返信用クラス
public class Todo { private Long id; private String usrName; private String taskName; @JsonSerialize(using = LocalDateTimeSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime date; // Getter, Setter, コンストラクタ省略 }
このサンプルアプリはタスク名と時刻をJSONに含めてリクエストを送信するとTodo型のオブジェクトに当てはめて、JSON形式で返却します。 リクエストの送信には、VS Code拡張のREST Clientを使用しています。
- リクエスト
POST http://localhost:8000/todo-app/todo-list/1 Content-Type: application/json { "taskName": "読書", "date": "2023-08-02T22:20:00" }
- レスポンス
HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Wed, 02 Aug 2023 15:20:54 GMT Connection: close [ { "id": 1, "usrName": "test001", "taskName": "読書", "date": "2023-08-02T22:20:00" } ]
現状のコードではバリデーションチェックがなにも行われていないため、Nullなどの値を送ってもそのままエラーチェックをすり抜けます。 例として、taskNameをリクエストボディに含めずリクエストを送信します。
- リクエスト
POST http://localhost:8000/todo-app/todo-list/1 Content-Type: application/json { "date": "2023-08-02T22:20:00" }
- レスポンス
HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Wed, 02 Aug 2023 15:11:12 GMT Connection: close [ { "id": 1, "usrName": "test001", "taskName": null, "date": "2023-08-02T22:20:00" } ]
リクエストに含まれていない"taskName"
がnull
になっているのが確認できます。
では、入力値のバリデーションチェックを行い、taskNameが指定されていない場合にエラーを返すようにします。
TodoクラスのtaskNameに@NotNull
アノテーションを付けます。
@NotNull private String taskName;
これで、Null値を許容しない設定にすることができました。 次にエラーハンドリングができるようにします。 コントローラーを次のように変更します。
@PostMapping("todo-list/{id}") public ResponseEntity<Object> post(@PathVariable Integer id, @RequestBody @Validated Todo todo, BindingResult result) { if (result.hasErrors()) { return new ResponseEntity<Object>("bad request", HttpStatus.BAD_REQUEST); } List<Todo> todoList = new ArrayList<>(); todo.setId(1L); todo.setUsrName("test001"); todoList.add(todo); return ResponseEntity.status(HttpStatus.OK).body(todoList); }
引数の部分にアノテーションを追加し、エラーハンドリングの処理を追加します。 先ほどと同じ、taskNameがないリクエストを送信します。 結果は次の通りです。
HTTP/1.1 400 Content-Type: text/plain;charset=UTF-8 Content-Length: 11 Date: Wed, 02 Aug 2023 15:33:01 GMT Connection: close bad request
エラーハンドリングの部分で設定したbad request
が返ってきたのが確認できました。
ネストされたformの入力値チェック
前項では、入力値を受け取るクラスのフィールド変数に対して簡単にバリデーションを行うことができることが確認できました。 しかし、フォームクラス内に自作クラスを使用してフィールド変数を持つ場合、従来の方法だとバリデーションチェックが正常に機能しないケースがあります。
具体的には、フォームクラスの変数自体はアノテーションでチェックできるものの、そのフィールド変数が自作クラスであり、かつ空の配列が入力された場合、入力値のチェックがうまく行われないことがあります。 実際に要素を追加して確認してみましょう。
public class Todo { private Long id; private String usrName; @NotNull private String taskName; @JsonSerialize(using = LocalDateTimeSerializer.class) @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") private LocalDateTime date; // 追加部分 private List<SubTask> subTasks; public static class SubTask { @NotNull private String taskName; // Getter Setter コンストラクタ省略 } // Getter Setter コンストラクタ省略
- リクエスト
POST http://localhost:8000/todo-app/todo-list/1 Content-Type: application/json { "taskName": "", "date": "2023-08-02T22:20:00", "subTasks": [ {} ] }
- レスポンス
HTTP/1.1 200 Content-Type: application/json Transfer-Encoding: chunked Date: Sun, 06 Aug 2023 15:34:33 GMT Connection: close [ { "id": 1, "usrName": "test001", "taskName": "", "date": "2023-08-02T22:20:00", "subTasks": [ { "taskName": null } ] } ]
subTaskのtaskNameが指定されていないにもかかわらず、バリデーションエラーが返ってきません。このことから、SubTask内のtaskNameに対して、@NotNull
は働いていないことがわかります。
この問題は、親クラスでフィールド変数を宣言する際に@Validアノテーションを付与することで解決できます。
@Valid private List<SubTask> subTasks;
これにより、オブジェクトのフィールド変数に対してもアノテーションチェックが行われるようになります。
- リクエスト
POST http://localhost:8000/todo-app/todo-list/1 Content-Type: application/json { "taskName": "", "date": "2023-08-02T22:20:00", "subTasks": [ {} ] }
- レスポンス
HTTP/1.1 400 Content-Type: text/plain;charset=UTF-8 Content-Length: 11 Date: Wed, 02 Aug 2023 15:33:01 GMT Connection: close bad request
バリデーションチェックが行われていることが確認できました。
まとめ
この記事では、Spring bootにおける入力値のバリデーションについて、調べて結果を紹介しました。 また、サンプルアプリを用いて、入力値のバリデーションを実践しました。 Spring Bootのバリデーションの理解の一助になれば幸いです。