RAKUS Developers Blog | ラクス エンジニアブログ

株式会社ラクスのITエンジニアによる技術ブログです。

【初心者向け】 Spring Bootにおける入力値チェック

はじめに

はじめまして、新卒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のバリデーションの理解の一助になれば幸いです。

参考文献

Copyright © RAKUS Co., Ltd. All rights reserved.