簡易書籍登録アプリケーションの説明(バリデーション版)

7.3 簡易書籍登録アプリケーションの説明(バリデーション版)

7.2で作成したアプリケーションをもとに、エンティティを利用したバリデーション処理が実際どのような流れで動作しているのかを説明していきます。

7.3.1 Bookクラス(エンティティクラス)

1 アプリケーション作成の目的

まず初めに、エンティティクラスから確認していきましょう。

package jp.co.f1.spring.bms.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.Id;
import jakarta.persistence.Column;
import jakarta.validation.constraints.NotEmpty;

@Entity
@Table(name="bookinfo")
public class Book {

	// ISBN
	@Id
	@Column(length = 20)
	@NotEmpty(message="ISBNを入力してください")
	private String isbn;

	public String getIsbn() {
		return isbn;
	}

	public void setIsbn(String isbn) {
		this.isbn = isbn;
	}

	// タイトル
	@Column(length = 100, nullable = true)
	@NotEmpty(message="タイトルを入力してください")
	private String title;

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	// 価格
	@Column(length = 11, nullable = true)
	@NotEmpty(message="価格を入力してください")
	private String price;

	public String getPrice() {
		return price;
	} 

	public void setPrice(String price) {
		this.price = price;
	}
	
}

ここでは全てのプロパティにバリデーション用のアノテーションを1つずつ追加しました。こうすることで、未入力を許可しないようにしています。 また、アノテーションの引数内に message=”○○” という形でエラーメッセージを指定しています。もしこのルールに該当した場合はここで設定した値がメッセージとして表示されます。どちらもとても簡単ですね。

バリデーションのルールはまだ他にも多く用意されていますが、まず先に一連の流れから確認していきましょう。

7.3.2 BookControllerクラス(コントローラークラス)

元々コントローラーでもエンティティを利用していましたが、今回はバリデーション処理を行うことがポイントとなります。それでは確認していきましょう。

package jp.co.f1.spring.bms.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;

import jp.co.f1.spring.bms.repository.BookRepository;
import jp.co.f1.spring.bms.entity.Book;

@Controller
public class BmsController {

	// Repositoryインターフェースを自動インスタンス化
	@Autowired
	private BookRepository bookinfo;

	・・・途中、省略・・・

	/*
	 * 「/insert」へアクセスがあった場合
	 */
	@GetMapping("/insert")
	public ModelAndView insert(@ModelAttribute Book book, ModelAndView mav) {

		// Viewに渡す変数をModelに格納
		mav.addObject("book", book);

		// 画面に出力するViewを指定
		mav.setViewName("insert");

		// ModelとView情報を返す
		return mav;
	}

	/*
	 * 「/insert」へPOST送信された場合
	 */
	@PostMapping(value = "/insert")
	// POSTデータをBookインスタンスとして受け取る
	public ModelAndView insertPost(@ModelAttribute @Validated Book book, BindingResult result, ModelAndView mav) {
		// 入力エラーがある場合
		if (result.hasErrors()) {
			// エラーメッセージ
			mav.addObject("message", "入力内容に誤りがあります");

			// 画面に出力するViewを指定
			mav.setViewName("insert");

			// ModelとView情報を返す
			return mav;
		}

		// 入力されたデータをDBに保存
		bookinfo.saveAndFlush(book);

		// リダイレクト先を指定
		mav = new ModelAndView("redirect:/list");

		// ModelとView情報を返す
		return mav;
	}
}

■@ValidatedとBindingResult
フォームが送信されると、insertPostメソッドが呼び出されます。このメソッドには、3つの引数が用意されています。これがここでのポイントとなります。

第1引数にはBookインスタンスが渡されますが、これには2つのアノテーションが付けられています。1つは、 @ModelAttribute。これは既におなじみですね。
もう1つは「@Validated」。これが、このエンティティの値をバリデーションチェックします。これを付けることで、エンティティの各値を自動的にチェックするようになるのです。

■バリデーションエラーのチェック
バリデーションチェックの結果は、その後にある「BindingResult」という引数で知ることができます。これはErrorsというインターフェースを継承するサブインターフェースで、その名の通りアノテーションを使って値をバインドした結果を得ます。
ここでいえば、@ModelAttributeでフォームの値からBookインスタンスを作成する際の結果がBindingResultで得られることになります。インスタンスを作成するとき、@Validatedによってバリデーションがチェックされていますから、このBindingResultを調べればその状況がわかるのです。

エラーが発生しているかどうかは、「hasErrors」メソッドで調べられます。これは名前の通り、エラーが起こっているかどうかを知るもので、trueならばエラー有り、falseならばエラー無し、となるのです。したがって、この結果がfalseならばそのままBookを保存すればいい、というわけです。

trueの場合は、必要な値をaddObjectして再び “/insert” に戻って再入力を行ないます。
falseの場合は保存し、”/list” にリダイレクトさせています。

7.3.3 insert.html(テンプレートファイル)

最後に、画面にデータを表示するテンプレートを見ていきましょう。

■エラーメッセージを出力する
バリデーションチェックの結果は、<input>の次にある<p>タグ部分で出力しています(<form>直前の<p>タグ部分では単純にコントローラーから渡された値を表示しているだけです。)
まず、<form>タグ自体には以下のようにbookオブジェクトが設定されています。

タグに、th:object=”${book}” という属性が用意されていますね。これは、非常に重要です。この後のエラーメッセージの表示処理は、th:objectを利用して、バリデーションチェックを行なったオブジェクトが設定されているタグ内に配置しなければいけないからです。
実際のエラー表示は、以下のように行なっています。

th:if で、${#fields.hasErrors(‘isbn’)} という変数式が設定されています。「#fields」は、エンティティの各フィールドをバリデーションチェックした結果などがまとめられたオブジェクトです。
このエラーメッセージの表示は、2つの点に注意する必要があります。1つは、「エラーが発生しているときだけ表示する」ということ。もう1つは、「このフィールド(isbn)のエラーだけを表示する」という点です。

hasErrors」は、引数に指定したフィールドにエラーが発生しているかどうかをチェックします。th:if を使い、これがtrueのときだけタグを表示させます。

エラーメッセージの表示は、「th:errors」という属性を利用します。

には th:object で Book がオブジェクトに設定されていますから、*{isbn} でそのisbn項目のみを指定できます。th:errorsに *{isbn} を指定することで、isbnのエラーメッセージを出力できるのです。

そしてその後には、「th:errorclass」という属性が用意されています。これは、エラーが発生した際に適用されるクラス名を指定します。これにより、「エラーが起きたらテキストを赤い表示に変える」ということを行なっています。


NEXT>> 7.4 jakarta.validationによるアノテーション