0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Spring Boot 開発備忘録 入力フォーム・バリデーション編

0
Posted at

はじめに

Spring Bootで入力画面を作成するときは、次のような処理が必要になります。

  • HTMLの入力値をJavaで受け取る
  • POSTリクエストを処理する
  • 未入力などの入力エラーを確認する
  • エラーメッセージをHTMLへ表示する
  • 登録後に一覧画面へ戻る

この記事では、Formクラスを使った入力値の受け取りと、バリデーションの基本的な書き方をまとめます。

特定のアプリに依存しないように、SampleFormという名前を使用します。

入力処理の基本的な流れ

画面から入力された値は、次のような流れで処理されます。

HTMLの入力フォーム
  ↓
POSTリクエスト
  ↓
Controller
  ↓
Formクラス
  ↓
Service
  ↓
登録・更新処理

Formクラスを作成する

Formクラスは、画面から入力された値を受け取るためのクラスです。

package com.example.sample.form; // 自分で作成

public class SampleForm { // 自分で作成

    private String sampleName; // 自分で作成

    public String getSampleName() {
        return sampleName;
    }

    public void setSampleName(String sampleName) {
        this.sampleName = sampleName;
    }
}

次の名前は自分で決めます。

SampleForm
sampleName
getSampleName()
setSampleName()

フィールド名がsampleNameの場合、getterとsetterは次の名前にします。

getSampleName()
setSampleName()

HTMLの入力フォームを作成する

Thymeleafを使用して、FormクラスとHTMLを紐付けます。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>サンプル登録</title>
</head>
<body>

    <form
        th:action="@{/samples}"
        th:object="${sampleForm}"
        method="post">

        <label for="sampleName">名前</label>

        <input
            id="sampleName"
            type="text"
            th:field="*{sampleName}">

        <button type="submit">登録</button>
    </form>

</body>
</html>

th:action

フォームの送信先URLを指定します。

th:action="@{/samples}"

この例では、入力内容を/samplesへ送信します。

method="post"

POSTリクエストとして送信します。

method="post"

th:object

フォームで使用するJavaオブジェクトを指定します。

th:object="${sampleForm}"

th:field

Formクラスのフィールドと入力欄を紐付けます。

th:field="*{sampleName}"

Formクラスのフィールド名と一致させます。

private String sampleName;
th:field="*{sampleName}"

入力画面を表示するController

入力画面を表示するときは、空のFormオブジェクトをModelへ登録します。

package com.example.sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import com.example.sample.form.SampleForm;

@Controller
public class SampleController {

    @GetMapping("/samples/new")
    public String showSampleForm(Model model) {

        model.addAttribute(
                "sampleForm",
                new SampleForm()
        );

        return "sample-form";
    }
}

HTMLのth:objectと、addAttribute()の属性名を一致させます。

Controller側です。

model.addAttribute("sampleForm", new SampleForm());

HTML側です。

th:object="${sampleForm}"

POSTリクエストで入力値を受け取る

画面から送信された値を、Controllerで受け取ります。

package com.example.sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.sample.form.SampleForm;

@Controller
public class SampleController {

    @PostMapping("/samples")
    public String registerSample(

            @ModelAttribute("sampleForm")
            SampleForm sampleForm) {

        String sampleName =
                sampleForm.getSampleName();

        return "redirect:/samples";
    }
}

@ModelAttributeは、送信された入力値をFormオブジェクトへ格納します。

@ModelAttribute("sampleForm")
SampleForm sampleForm

入力値はgetterで取得できます。

String sampleName =
        sampleForm.getSampleName();

Serviceへ登録処理を渡す

Controllerで受け取った値を、Serviceへ渡します。

package com.example.sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.sample.form.SampleForm;
import com.example.sample.service.SampleService;

@Controller
public class SampleController {

    private final SampleService sampleService;

    public SampleController(
            SampleService sampleService) {

        this.sampleService = sampleService;
    }

    @PostMapping("/samples")
    public String registerSample(

            @ModelAttribute("sampleForm")
            SampleForm sampleForm) {

        sampleService.createSample(
                sampleForm.getSampleName()
        );

        return "redirect:/samples";
    }
}

Serviceの例です。

package com.example.sample.service;

import org.springframework.stereotype.Service;

@Service
public class SampleService {

    public void createSample(String sampleName) {

        // 登録処理などを記載する
        System.out.println(sampleName);
    }
}

@NotBlankで未入力をチェックする

Formクラスのフィールドに@NotBlankを付けます。

package com.example.sample.form;

import jakarta.validation.constraints.NotBlank;

public class SampleForm {

    @NotBlank(message = "名前を入力してください")
    private String sampleName;

    public String getSampleName() {
        return sampleName;
    }

    public void setSampleName(String sampleName) {
        this.sampleName = sampleName;
    }
}

@NotBlankは、次の値をエラーにします。

  • null
  • 空文字
  • 空白だけの文字列

@NotBlankはJakartaが用意しています。

エラーメッセージは自分で決めます。

@NotBlank(message = "名前を入力してください")

Controllerでバリデーションを実行する

ControllerのForm引数に@Validatedを付けます。

package com.example.sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.sample.form.SampleForm;

@Controller
public class SampleController {

    @PostMapping("/samples")
    public String registerSample(

            @Validated
            @ModelAttribute("sampleForm")
            SampleForm sampleForm,

            BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            return "sample-form";
        }

        return "redirect:/samples";
    }
}

@Validated

Formに設定した入力チェックを実行します。

@Validated

BindingResult

入力チェックの結果を受け取ります。

BindingResult bindingResult

エラーがあるかどうかは、hasErrors()で確認します。

if (bindingResult.hasErrors()) {
    return "sample-form";
}

BindingResultを書く位置

BindingResultは、入力チェックするFormの直後に書きます。

正しい例です。

@Validated
@ModelAttribute("sampleForm")
SampleForm sampleForm,

BindingResult bindingResult

FormとBindingResultの間に、別の引数を入れないようにします。

避ける例です。

@Validated
@ModelAttribute("sampleForm")
SampleForm sampleForm,

Model model,

BindingResult bindingResult

HTMLへエラーメッセージを表示する

th:errorsを使用して、Formに設定したエラーメッセージを表示します。

<form
    th:action="@{/samples}"
    th:object="${sampleForm}"
    method="post">

    <label for="sampleName">名前</label>

    <input
        id="sampleName"
        type="text"
        th:field="*{sampleName}">

    <p th:errors="*{sampleName}"></p>

    <button type="submit">登録</button>
</form>

Formクラスのフィールドです。

private String sampleName;

HTMLの入力欄です。

th:field="*{sampleName}"

HTMLのエラー表示です。

th:errors="*{sampleName}"

すべてsampleNameで一致させます。

入力処理の全体例

Form

package com.example.sample.form;

import jakarta.validation.constraints.NotBlank;

public class SampleForm {

    @NotBlank(message = "名前を入力してください")
    private String sampleName;

    public String getSampleName() {
        return sampleName;
    }

    public void setSampleName(String sampleName) {
        this.sampleName = sampleName;
    }
}

Controller

package com.example.sample.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import com.example.sample.form.SampleForm;
import com.example.sample.service.SampleService;

@Controller
public class SampleController {

    private final SampleService sampleService;

    public SampleController(
            SampleService sampleService) {

        this.sampleService = sampleService;
    }

    @GetMapping("/samples/new")
    public String showSampleForm(Model model) {

        model.addAttribute(
                "sampleForm",
                new SampleForm()
        );

        return "sample-form";
    }

    @PostMapping("/samples")
    public String registerSample(

            @Validated
            @ModelAttribute("sampleForm")
            SampleForm sampleForm,

            BindingResult bindingResult) {

        if (bindingResult.hasErrors()) {
            return "sample-form";
        }

        sampleService.createSample(
                sampleForm.getSampleName()
        );

        return "redirect:/samples";
    }
}

HTML

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>サンプル登録</title>
</head>
<body>

    <form
        th:action="@{/samples}"
        th:object="${sampleForm}"
        method="post">

        <label for="sampleName">名前</label>

        <input
            id="sampleName"
            type="text"
            th:field="*{sampleName}">

        <p th:errors="*{sampleName}"></p>

        <button type="submit">登録</button>
    </form>

</body>
</html>

登録後にリダイレクトする

登録処理が完了したら、一覧画面へリダイレクトします。

return "redirect:/samples";

処理の流れです。

POSTで登録
  ↓
登録処理が完了
  ↓
/samplesへリダイレクト
  ↓
GETで一覧画面を表示

登録後にリダイレクトすることで、ブラウザを更新したときの二重登録を防ぎやすくなります。

入力チェックが動かないときの確認項目

入力チェックが動作しない場合は、次の内容を確認します。

  1. Formのフィールドに@NotBlankなどが付いているか
  2. ControllerのForm引数に@Validatedが付いているか
  3. BindingResultがFormの直後にあるか
  4. HTMLにth:errorsがあるか
  5. Validationの依存関係が追加されているか

必要な構成を簡単にまとめると、次のようになります。

Form
  └─ @NotBlank

Controller
  ├─ @Validated
  └─ BindingResult

HTML
  └─ th:errors

まとめ

画面入力を受け取るときは、次の役割に分けます。

Form
  → 入力値を保持する

HTML
  → 入力欄とFormを紐付ける

Controller
  → POSTリクエストを受け取る

Service
  → 登録や更新の処理を行う

バリデーションでは、次の4つを使用します。

@NotBlank
@Validated
BindingResult
th:errors

次の記事では、外部APIの呼び出しと、データベースの基本操作についてまとめます。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?