【Spring Boot】バリデーションエラーを日本語化してメッセージを表示する|簡単な掲示板アプリの作成
この記事では、Spring Bootで簡単な掲示板アプリを作成しながら、入力値のバリデーション機能を実装する方法を学びます。DTOとバリデーションアノテーションを使ってデータの制約を定義し、エラーが発生した際に日本語で分かりやすいメッセージを表示する手順を、具体的なコードを交えて解説します。
開発環境
- OS: Windows10
- Visual Studio Code: 1.73.0
- Java: OpenJDK 23
- Spring Boot: 3.4.0
- Lombok: 1.18.30
- Thymeleaf Layout Dialect: 3.3.0
- Spring Security: 6.1.1
- Thymeleaf Extras Spring Security: 3.1.2.RELEASE
- maven-compiler-plugin: 3.8.1
- Spring Boot Starter Mail: 3.4.0
- Spring Boot Starter Validation: 3.4.0
サンプルコード
/pom.xml
/pom.xml1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-mail</artifactId> 4- </dependency> 5+ </dependency> 6+ <dependency> 7+ <groupId>org.springframework.boot</groupId> 8+ <artifactId>spring-boot-starter-validation</artifactId> 9+ </dependency> 10 </dependencies> 11 12 <build> 13
/src/main/java/com/example/bbs/controller/CommentController.java
/src/main/java/com/example/bbs/controller/CommentController.java1 package com.example.bbs.controller; 2 3+import com.example.bbs.dto.CommentForm; 4 import com.example.bbs.model.Comment; 5 import com.example.bbs.model.Post; 6 import com.example.bbs.model.User; 7 import com.example.bbs.service.CommentService; 8 import com.example.bbs.service.PostService; 9 import com.example.bbs.service.UserService; 10+ 11+import jakarta.validation.Valid; 12+ 13 import org.springframework.stereotype.Controller; 14+import org.springframework.validation.BindingResult; 15 import org.springframework.web.bind.annotation.*; 16+import org.springframework.web.servlet.mvc.support.RedirectAttributes; 17 18 @Controller 19 @RequestMapping("/comments") 20 21 // コメントの追加 22 @PostMapping("/add") 23- public String addComment(@RequestParam Long postId, @RequestParam String content) { 24+ public String addComment( 25+ @RequestParam Long postId, 26+ @Valid @ModelAttribute("commentForm") CommentForm commentForm, 27+ BindingResult result, 28+ RedirectAttributes ra) { 29+ 30+ // バリデーションエラーがある場合、エラー情報を含めたフォーム画面へ戻す 31+ if (result.hasErrors()) { 32+ // バリデーションエラーがあればリダイレクト先にエラー情報を渡す 33+ ra.addFlashAttribute("org.springframework.validation.BindingResult.commentForm", result); 34+ ra.addFlashAttribute("commentForm", commentForm); 35+ return "redirect:/posts/" + postId; // 元の投稿詳細画面にリダイレクト 36+ 37+ } 38+ 39 Post post = postService.findById(postId).orElseThrow(); 40 User user = userService.getCurrentUser(); // 現在のログイン中ユーザーを取得 41 42 Comment comment = new Comment(); 43 comment.setPost(post); 44 comment.setUser(user); // ユーザーを設定 45- comment.setContent(content); 46+ comment.setContent(commentForm.getContent()); 47 48 commentService.save(comment); 49 return "redirect:/posts/" + postId; 50
/src/main/java/com/example/bbs/controller/PostController.java
/src/main/java/com/example/bbs/controller/PostController.java1 package com.example.bbs.controller; 2 3+import com.example.bbs.dto.CommentForm; 4+import com.example.bbs.dto.PostForm; 5 import com.example.bbs.model.Post; 6 import com.example.bbs.model.User; 7 import com.example.bbs.service.PostService; 8 import com.example.bbs.service.UserService; 9+ 10+import jakarta.validation.Valid; 11+ 12 import com.example.bbs.service.CommentService; 13 import com.example.bbs.service.LikeService; 14 15 import org.springframework.data.domain.Page; 16 import org.springframework.stereotype.Controller; 17 import org.springframework.ui.Model; 18+import org.springframework.validation.BindingResult; 19 import org.springframework.web.bind.annotation.*; 20 21 @Controller 22 // コメント情報をモデルに渡す 23 model.addAttribute("comments", commentService.findByPostId(id)); 24 25+ // コメントフォームを追加 26+ if (!model.containsAttribute("commentForm")) { 27+ model.addAttribute("commentForm", new CommentForm()); 28+ } 29+ 30 // ログインユーザーがこの投稿に「いいね」しているかどうかを判定 31 boolean isLiked = likeService.isLikedByUser(post, loggedInUser); 32 model.addAttribute("isLiked", isLiked); 33 34 // 投稿作成 35 @PostMapping 36- public String createPost(@ModelAttribute Post post) { 37+ public String createPost(@Valid @ModelAttribute("post") PostForm postForm, BindingResult result, Model model) { 38+ // バリデーションエラーがある場合、エラー情報を含めたフォーム画面へ戻す 39+ if (result.hasErrors()) { 40+ model.addAttribute("post", postForm); 41+ return "posts/new"; // 例:postForm.html というテンプレート名 42+ } 43+ 44 // 現在のログイン中のユーザーを取得 45 User user = userService.getCurrentUser(); // ユーザー取得メソッド 46 47- // ユーザーをPostに設定 48+ // バリデーションが通ったら、DTOの内容をエンティティに変換 49+ Post post = new Post(); 50+ post.setTitle(postForm.getTitle()); 51+ post.setContent(postForm.getContent()); 52 post.setUser(user); 53 54 postService.save(post); 55
/src/main/java/com/example/bbs/dto/CommentForm.java
/src/main/java/com/example/bbs/dto/CommentForm.java1+package com.example.bbs.dto; 2+ 3+import jakarta.validation.constraints.NotBlank; 4+import jakarta.validation.constraints.Size; 5+import lombok.Getter; 6+import lombok.Setter; 7+ 8+@Getter 9+@Setter 10+public class CommentForm { 11+ 12+ @NotBlank(message = "{error.comment.content.blank}") 13+ @Size(max = 100, message = "{error.comment.content.size}") 14+ private String content; 15+} 16
/src/main/java/com/example/bbs/dto/PostForm.java
/src/main/java/com/example/bbs/dto/PostForm.java1+package com.example.bbs.dto; 2+ 3+import jakarta.validation.constraints.NotBlank; 4+import jakarta.validation.constraints.Size; 5+import lombok.Getter; 6+import lombok.Setter; 7+ 8+@Getter 9+@Setter 10+public class PostForm { 11+ 12+ @NotBlank(message = "{error.post.title.blank}") 13+ @Size(max = 100, message = "{error.post.title.size}") 14+ private String title; 15+ 16+ @NotBlank(message = "{error.post.content.blank}") 17+ @Size(max = 1000, message = "{error.post.content.size}") 18+ private String content; 19+} 20
/src/main/resources/application.properties
/src/main/resources/application.properties1 spring.application.name=bbs 2 3+# メッセージプロパティ 4+spring.messages.basename=messages 5+spring.messages.encoding=UTF-8 6+ 7 # MySQLの設定 8 spring.datasource.url=jdbc:mysql://localhost:3306/board 9 spring.datasource.username=root 10
/src/main/resources/messages.properties
/src/main/resources/messages.properties1+error.post.title.blank=タイトルは必須です。 2+error.post.title.size=タイトルは最大 {max} 文字以内で入力してください。 3+error.post.content.blank=内容は必須です。 4+error.post.content.size=内容は最大 {max} 文字以内で入力してください。 5+error.comment.content.blank=コメントを入力してください 6+error.comment.content.size=コメントは{max}文字以内で入力してください 7
/src/main/resources/templates/posts/detail.html
/src/main/resources/templates/posts/detail.html1 2 <!-- コメント追加フォーム --> 3 <h3>Add a Comment</h3> 4- <form th:action="@{/comments/add}" method="post"> 5+ <form th:action="@{/comments/add}" method="post" th:object="${commentForm}"> 6 <input type="hidden" name="postId" th:value="${post.id}"/> 7 <div class="mb-3"> 8- <textarea name="content" class="form-control" rows="3" placeholder="Write a comment..." required></textarea> 9+ <textarea name="content" class="form-control" rows="3" placeholder="Write a comment..." required th:field="*{content}"></textarea> 10+ <div th:if="${#fields.hasErrors('content')}" th:errors="*{content}" class="text-danger"></div> 11 </div> 12 <button type="submit" class="btn btn-success">Add Comment</button> 13 </form> 14
/src/main/resources/templates/posts/new.html
/src/main/resources/templates/posts/new.html1 2 <main layout:fragment="content"> 3 <h1>New Post</h1> 4- <form th:action="@{/posts}" method="post"> 5+ <form th:action="@{/posts}" method="post" th:object="${post}"> 6 <div class="mb-3"> 7 <label for="title" class="form-label">Title</label> 8- <input type="text" id="title" name="title" class="form-control" required> 9+ <input type="text" id="title" name="title" class="form-control" required th:field="*{title}"> 10+ <div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="text-danger"></div> 11 </div> 12 <div class="mb-3"> 13 <label for="content" class="form-label">Content</label> 14- <textarea id="content" name="content" class="form-control" rows="5" required></textarea> 15+ <textarea id="content" name="content" class="form-control" rows="5" required th:field="*{content}"></textarea> 16+ <div th:if="${#fields.hasErrors('content')}" th:errors="*{content}" class="text-danger"></div> 17 </div> 18 <button type="submit" class="btn btn-success">Submit</button> 19 </form> 20
コード解説
変更点: バリデーションライブラリの追加
/pom.xml1 <dependency> 2 <groupId>org.springframework.boot</groupId> 3 <artifactId>spring-boot-starter-mail</artifactId> 4- </dependency> 5+ </dependency> 6+ <dependency> 7+ <groupId>org.springframework.boot</groupId> 8+ <artifactId>spring-boot-starter-validation</artifactId> 9+ </dependency> 10 </dependencies> 11 12 <build>
ここでは、プロジェクトの依存関係を管理するpom.xmlファイルに、新しくバリデーション機能を使うためのライブラリを追加しています。spring-boot-starter-validationという依存関係を追加することで、Spring Bootアプリケーションで入力値の検証(バリデーション)を行うために必要なツールが自動的にプロジェクトに組み込まれます。これには、データの制約を定義するためのjakarta.validation(Jakarta Bean Validation API)と、その定義に基づいて実際に検証を行うためのHibernate Validatorという実装が含まれています。このライブラリを追加することで、後述するアノテーションを使って簡単にバリデーションを実装できるようになります。
変更点: 投稿用フォームデータ(PostForm)の作成とバリデーション定義
/src/main/java/com/example/bbs/dto/PostForm.java1+package com.example.bbs.dto; 2+ 3+import jakarta.validation.constraints.NotBlank; 4+import jakarta.validation.constraints.Size; 5+import lombok.Getter; 6+import lombok.Setter; 7+ 8+@Getter 9+@Setter 10+public class PostForm { 11+ 12+ @NotBlank(message = "{error.post.title.blank}") 13+ @Size(max = 100, message = "{error.post.title.size}") 14+ private String title; 15+ 16+ @NotBlank(message = "{error.post.content.blank}") 17+ @Size(max = 1000, message = "{error.post.content.size}") 18+ private String content; 19+}
ここでは、ユーザーが投稿を作成する際に入力するデータを一時的に保持するためのPostFormというクラスを新しく作成しています。このようなクラスをData Transfer Object(DTO)と呼び、画面からの入力データを受け取ったり、画面に表示するデータを渡したりする役割を担います。@Getterと@Setterは、Lombokというライブラリのアノテーションで、記述するだけで自動的にgetterメソッドとsetterメソッドを生成してくれるため、コード量を減らすことができます。
titleとcontentというフィールドには、入力値の制約(バリデーションルール)を定義するアノテーションが付与されています。
@NotBlank: そのフィールドが空でないこと(空白文字のみも不可)を要求します。@Size(max = 100): 文字列の長さが指定した最大値(ここでは100文字)を超えないことを要求します。 これらのアノテーションに設定されているmessage = "{error.post.title.blank}"のような部分は、もしバリデーションルールに違反した場合に表示するエラーメッセージの「キー」を指定しています。このキーに対応する実際の日本語メッセージは、後述するmessages.propertiesファイルで定義します。
変更点: コメント用フォームデータ(CommentForm)の作成とバリデーション定義
/src/main/java/com/example/bbs/dto/CommentForm.java1+package com.example.bbs.dto; 2+ 3+import jakarta.validation.constraints.NotBlank; 4+import jakarta.validation.constraints.Size; 5+import lombok.Getter; 6+import lombok.Setter; 7+ 8+@Getter 9+@Setter 10+public class CommentForm { 11+ 12+ @NotBlank(message = "{error.comment.content.blank}") 13+ @Size(max = 100, message = "{error.comment.content.size}") 14+ private String content; 15+}
こちらもPostFormと同様に、ユーザーがコメントを投稿する際に入力するデータを一時的に保持するためのCommentFormというDTOクラスを新しく作成しています。@Getterと@SetterはLombokのアノテーションです。
contentフィールドには、以下のバリデーションアノテーションが付与されています。
@NotBlank: コメントの内容が空でないことを要求します。@Size(max = 100): コメントの内容が100文字を超えないことを要求します。 これらのアノテーションのmessage属性には、バリデーションエラーが発生した場合に表示するメッセージのキーを指定しています。これにより、messages.propertiesファイルで定義された日本語のエラーメッセージが使用されます。
変更点: エラーメッセージファイルの設定
/src/main/resources/application.properties1 spring.application.name=bbs 2 3+# メッセージプロパティ 4+spring.messages.basename=messages 5+spring.messages.encoding=UTF-8 6+ 7 # MySQLの設定 8 spring.datasource.url=jdbc:mysql://localhost:3306/board 9 spring.datasource.username=root
ここでは、Spring Bootアプリケーションの設定ファイルであるapplication.propertiesに、エラーメッセージをどこから読み込むかを指定する設定を追加しています。
spring.messages.basename=messages: これは、アプリケーションがエラーメッセージやその他の国際化メッセージを読み込むファイルの「ベース名」を指定しています。この設定により、src/main/resources/messages.propertiesというファイルがメッセージの定義元として認識されます。spring.messages.encoding=UTF-8: メッセージファイルがUTF-8エンコーディングで保存されていることを指定しています。これにより、日本語などのマルチバイト文字が正しく表示されるようになります。
変更点: 日本語エラーメッセージの定義
/src/main/resources/messages.properties1+error.post.title.blank=タイトルは必須です。 2+error.post.title.size=タイトルは最大 {max} 文字以内で入力してください。 3+error.post.content.blank=内容は必須です。 4+error.post.content.size=内容は最大 {max} 文字以内で入力してください。 5+error.comment.content.blank=コメントを入力してください 6+error.comment.content.size=コメントは{max}文字以内で入力してください
ここでは、src/main/resources/messages.propertiesというファイルに、アプリケーション全体で使用する日本語のメッセージ、特にバリデーションエラーメッセージを定義しています。先ほどPostFormやCommentFormで指定したmessage = "{キー}"の「キー」に対応する実際のメッセージを記述しています。
例えば、error.post.title.blank=タイトルは必須です。という行は、PostFormのtitleフィールドで@NotBlankアノテーションのバリデーションに失敗した場合に「タイトルは必須です。」というメッセージが表示されるように設定しています。
また、error.post.title.size=タイトルは最大 {max} 文字以内で入力してください。のように、{max}という部分には、バリデーションアノテーション(例: @Size(max = 100))で設定されたmax値が自動的に埋め込まれて表示されます。これにより、動的なメッセージを柔軟に作成できます。
変更点: PostControllerでの投稿作成処理にバリデーションを適用
/src/main/java/com/example/bbs/controller/PostController.java1 package com.example.bbs.controller; 2 3+import com.example.bbs.dto.CommentForm; 4+import com.example.bbs.dto.PostForm; 5 import com.example.bbs.model.Post; 6 import com.example.bbs.model.User; 7 import com.example.bbs.service.PostService; 8 import com.example.bbs.service.UserService; 9+ 10+import jakarta.validation.Valid; 11+ 12 import com.example.bbs.service.CommentService; 13 import com.example.bbs.service.LikeService; 14 15 import org.springframework.data.domain.Page; 16 import org.springframework.stereotype.Controller; 17 import org.springframework.ui.Model; 18+import org.springframework.validation.BindingResult; 19 import org.springframework.web.bind.annotation.*; 20 21 @Controller 22 // コメント情報をモデルに渡す 23 model.addAttribute("comments", commentService.findByPostId(id)); 24 25+ // コメントフォームを追加 26+ if (!model.containsAttribute("commentForm")) { 27+ model.addAttribute("commentForm", new CommentForm()); 28+ } 29+ 30 // ログインユーザーがこの投稿に「いいね」しているかどうかを判定 31 boolean isLiked = likeService.isLikedByUser(post, loggedInUser); 32 model.addAttribute("isLiked", isLiked); 33 34 // 投稿作成 35 @PostMapping 36- public String createPost(@ModelAttribute Post post) { 37+ public String createPost(@Valid @ModelAttribute("post") PostForm postForm, BindingResult result, Model model) { 38+ // バリデーションエラーがある場合、エラー情報を含めたフォーム画面へ戻す 39+ if (result.hasErrors()) { 40+ model.addAttribute("post", postForm); 41+ return "posts/new"; // 例:postForm.html というテンプレート名 42+ } 43+ 44 // 現在のログイン中のユーザーを取得 45 User user = userService.getCurrentUser(); // ユーザー取得メソッド 46 47 // ユーザーをPostに設定 48+ // バリデーションが通ったら、DTOの内容をエンティティに変換 49+ Post post = new Post(); 50+ post.setTitle(postForm.getTitle()); 51+ post.setContent(postForm.getContent()); 52 post.setUser(user); 53 54 postService.save(post);
この変更では、投稿を作成するcreatePostメソッドにバリデーション機能を導入しています。
- メソッドの引数が
@ModelAttribute Post postから@Valid @ModelAttribute("post") PostForm postForm, BindingResult result, Model modelに変更されました。@ModelAttribute("post") PostForm postForm: 以前は直接Postエンティティを受け取っていましたが、ここでは新しく作成したPostFormDTOを受け取るように変更しています。"post"という名前は、Thymeleafテンプレートのフォーム (th:object="${post}") と対応します。@Valid: このアノテーションをPostFormの前に付けることで、SpringがPostFormクラスに定義されたバリデーションルール(@NotBlankや@Sizeなど)を実行するよう指示します。BindingResult result: バリデーションの結果(エラーがあったかどうか、どのようなエラーか)がこのBindingResultオブジェクトに格納されます。これは、@Validアノテーションの直後に記述する必要があります。Model model: 画面にデータを渡すためのオブジェクトです。
if (result.hasErrors())ブロックが追加されました。result.hasErrors()は、バリデーションエラーが発生したかどうかを判定します。もしエラーがあればtrueを返します。- エラーがある場合、
model.addAttribute("post", postForm);でエラーが発生したpostFormオブジェクトを再度画面に渡し、return "posts/new";で投稿フォームの画面にリダイレクトせずに直接戻るようにしています。これにより、ユーザーは入力したデータやエラーメッセージを確認しながら修正できます。
- バリデーションが成功した場合(
ifブロックに入らなかった場合)に、PostFormからPostエンティティにデータを手動で詰め替える処理 (Post post = new Post(); post.setTitle(postForm.getTitle()); ...) が追加されました。DTOは画面とのデータのやり取りに特化しているため、データベースに保存する際には本来のエンティティに変換する必要があります。
変更点: PostControllerでの投稿詳細画面にコメントフォームの準備
/src/main/java/com/example/bbs/controller/PostController.java1 // コメント情報をモデルに渡す 2 model.addAttribute("comments", commentService.findByPostId(id)); 3 4+ // コメントフォームを追加 5+ if (!model.containsAttribute("commentForm")) { 6+ model.addAttribute("commentForm", new CommentForm()); 7+ } 8+ 9 // ログインユーザーがこの投稿に「いいね」しているかどうかを判定 10 boolean isLiked = likeService.isLikedByUser(post, loggedInUser); 11 model.addAttribute("isLiked", isLiked);
この変更は、投稿の詳細画面を表示するdetailメソッド内で、コメントフォームを適切に扱うための準備です。
model.addAttribute("commentForm", new CommentForm());という行で、新しいCommentFormオブジェクトをモデルに追加しています。これは、詳細画面にコメント投稿用のフォームが表示される際に、そのフォームとデータを紐づけるための空のCommentFormオブジェクトを提供するためです。
if (!model.containsAttribute("commentForm"))という条件が追加されているのは、特に重要です。コメント投稿時にバリデーションエラーが発生して、CommentControllerから詳細画面にリダイレクトされる場合、RedirectAttributesによってエラー情報と共にcommentFormオブジェクトがすでにモデルに追加されている可能性があります。この条件があることで、リダイレクトされてきた場合には既存のcommentForm(ユーザーの入力値とエラー情報を含む)が上書きされず、エラーメッセージが正しく表示されるようになります。
変更点: CommentControllerでのコメント追加処理にバリデーションを適用
/src/main/java/com/example/bbs/controller/CommentController.java1 package com.example.bbs.controller; 2 3+import com.example.bbs.dto.CommentForm; 4 import com.example.bbs.model.Comment; 5 import com.example.bbs.model.Post; 6 import com.example.bbs.model.User; 7 import com.example.bbs.service.CommentService; 8 import com.example.bbs.service.PostService; 9 import com.example.bbs.service.UserService; 10+ 11+import jakarta.validation.Valid; 12+ 13 import org.springframework.stereotype.Controller; 14+import org.springframework.validation.BindingResult; 15 import org.springframework.web.bind.annotation.*; 16+import org.springframework.web.servlet.mvc.support.RedirectAttributes; 17 18 @Controller 19 @RequestMapping("/comments") 20 21 // コメントの追加 22 @PostMapping("/add") 23- public String addComment(@RequestParam Long postId, @RequestParam String content) { 24+ public String addComment( 25+ @RequestParam Long postId, 26+ @Valid @ModelAttribute("commentForm") CommentForm commentForm, 27+ BindingResult result, 28+ RedirectAttributes ra) { 29+ 30+ // バリデーションエラーがある場合、エラー情報を含めたフォーム画面へ戻す 31+ if (result.hasErrors()) { 32+ // バリデーションエラーがあればリダイレクト先にエラー情報を渡す 33+ ra.addFlashAttribute("org.springframework.validation.BindingResult.commentForm", result); 34+ ra.addFlashAttribute("commentForm", commentForm); 35+ return "redirect:/posts/" + postId; // 元の投稿詳細画面にリダイレクト 36+ 37+ } 38+ 39 Post post = postService.findById(postId).orElseThrow(); 40 User user = userService.getCurrentUser(); // 現在のログイン中ユーザーを取得 41 42 Comment comment = new Comment(); 43 comment.setPost(post); 44 comment.setUser(user); // ユーザーを設定 45- comment.setContent(content); 46+ comment.setContent(commentForm.getContent()); 47 48 commentService.save(comment); 49 return "redirect:/posts/" + postId;
この変更では、コメントを追加するaddCommentメソッドにバリデーション機能を導入しています。
- メソッドの引数が
@RequestParam Long postId, @RequestParam String contentから@RequestParam Long postId, @Valid @ModelAttribute("commentForm") CommentForm commentForm, BindingResult result, RedirectAttributes raに変更されました。@ModelAttribute("commentForm") CommentForm commentForm: コメントの入力データを受け取るために、新しく作成したCommentFormDTOを使用しています。"commentForm"という名前は、Thymeleafテンプレートのフォーム (th:object="${commentForm}") と対応します。@Valid:CommentFormに定義されたバリデーションルールを実行するよう指示します。BindingResult result: バリデーションの結果がこのオブジェクトに格納されます。RedirectAttributes ra: これは、リダイレクト先にデータを渡すための特別なオブジェクトです。特に、リダイレクト後に一度だけ表示したいメッセージや、今回のバリデーションエラー情報のようなデータを渡すのに使用します。addFlashAttributeを使うと、一度だけセッションに保存され、リダイレクト先の画面で取得された後、自動的に削除されます。
if (result.hasErrors())ブロックが追加されました。result.hasErrors()でバリデーションエラーがあるか確認します。- エラーがある場合、
ra.addFlashAttribute("org.springframework.validation.BindingResult.commentForm", result);でBindingResultオブジェクトを、ra.addFlashAttribute("commentForm", commentForm);でエラーが発生したcommentFormオブジェクトをリダイレクト先に渡しています。これらは、リダイレクト後の詳細画面でエラーメッセージや入力値が再表示されるために使われます。 return "redirect:/posts/" + postId;で、元の投稿詳細画面にリダイレクトします。RedirectAttributesのおかげで、リダイレクトしてもエラー情報が失われずに次のページで利用できます。
- バリデーションが成功した場合、
CommentFormからCommentエンティティにデータを詰め替える処理 (comment.setContent(commentForm.getContent());) が行われます。
変更点: 投稿フォーム(new.html)でのバリデーションエラー表示
/src/main/resources/templates/posts/new.html1 2 <main layout:fragment="content"> 3 <h1>New Post</h1> 4- <form th:action="@{/posts}" method="post"> 5+ <form th:action="@{/posts}" method="post" th:object="${post}"> 6 <div class="mb-3"> 7 <label for="title" class="form-label">Title</label> 8- <input type="text" id="title" name="title" class="form-control" required> 9+ <input type="text" id="title" name="title" class="form-control" required th:field="*{title}"> 10+ <div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="text-danger"></div> 11 </div> 12 <div class="mb-3"> 13 <label for="content" class="form-label">Content</label> 14- <textarea id="content" name="content" class="form-control" rows="5" required></textarea> 15+ <textarea id="content" name="content" class="form-control" rows="5" required th:field="*{content}"></textarea> 16+ <div th:if="${#fields.hasErrors('content')}" th:errors="*{content}" class="text-danger"></div> 17 </div> 18 <button type="submit" class="btn btn-success">Submit</button> 19 </form>
この変更では、posts/new.html(新規投稿フォーム)にバリデーションエラーメッセージを表示する機能を追加しています。
<form th:action="@{/posts}" method="post" th:object="${post}">:<form>タグにth:object="${post}"が追加されました。これは、このフォームがControllerから渡された"post"という名前のオブジェクト(PostFormインスタンス)と紐付いていることをThymeleafに伝えます。これにより、フォームの入力フィールドとPostFormのプロパティを簡単に連携できるようになります。<input type="text" ... th:field="*{title}">: 入力フィールドのname属性が削除され、代わりにth:field="*{title}"が追加されました。th:fieldは、th:objectで指定したオブジェクトのプロパティ(ここではtitle)とこの入力フィールドを双方向にバインドします。これにより、フォームに初期値を表示したり、ユーザーの入力値をPostFormオブジェクトに自動的に設定したりできるようになります。<div th:if="${#fields.hasErrors('title')}" th:errors="*{title}" class="text-danger"></div>: この行が追加され、バリデーションエラーメッセージを表示するための部分が実装されました。th:if="${#fields.hasErrors('title')}": これは、titleフィールドにバリデーションエラーがある場合にのみ、この<div>要素が表示されるようにする条件です。#fieldsはThymeleafが提供するオブジェクトで、フォームフィールドのエラー情報を扱うための便利な機能です。th:errors="*{title}":titleフィールドに発生したすべてのバリデーションエラーメッセージをここに表示します。messages.propertiesで定義した日本語メッセージがここに自動的に挿入されます。 同様の変更がcontentフィールドにも適用されており、タイトルと内容の両方でバリデーションエラーがあった場合にユーザーに分かりやすいメッセージが表示されるようになります。
変更点: コメントフォーム(detail.html)でのバリデーションエラー表示
/src/main/resources/templates/posts/detail.html1 2 <!-- コメント追加フォーム --> 3 <h3>Add a Comment</h3> 4- <form th:action="@{/comments/add}" method="post"> 5+ <form th:action="@{/comments/add}" method="post" th:object="${commentForm}"> 6 <input type="hidden" name="postId" th:value="${post.id}"/> 7 <div class="mb-3"> 8- <textarea name="content" class="form-control" rows="3" placeholder="Write a comment..." required></textarea> 9+ <textarea name="content" class="form-control" rows="3" placeholder="Write a comment..." required th:field="*{content}"></textarea> 10+ <div th:if="${#fields.hasErrors('content')}" th:errors="*{content}" class="text-danger"></div> 11 </div> 12 <button type="submit" class="btn btn-success">Add Comment</button> 13 </form>
この変更では、posts/detail.html(投稿詳細画面)内のコメント追加フォームにバリデーションエラーメッセージを表示する機能を追加しています。
<form th:action="@{/comments/add}" method="post" th:object="${commentForm}">: フォームがControllerから渡された"commentForm"という名前のオブジェクト(CommentFormインスタンス)と紐付くようにth:objectが設定されました。これは、PostControllerのdetailメソッドでmodel.addAttribute("commentForm", new CommentForm());によって追加されたオブジェクトや、CommentControllerでエラー発生時にRedirectAttributesを通じて渡されたオブジェクトです。<textarea name="content" ... th:field="*{content}"></textarea>: コメント内容の入力フィールドがth:field="*{content}"と紐付けられました。これにより、CommentFormのcontentプロパティとフォームの入力値が連携されます。エラー発生時にRedirectAttributesで渡された入力値が自動的にフォームに再表示されます。<div th:if="${#fields.hasErrors('content')}" th:errors="*{content}" class="text-danger"></div>:contentフィールドにバリデーションエラーがある場合に、エラーメッセージを表示するための部分が追加されました。#fields.hasErrors('content')でエラーの有無を確認し、th:errors="*{content}"でmessages.propertiesに定義された日本語エラーメッセージが表示されます。 これらの変更により、コメント投稿時にバリデーションエラーが発生した場合でも、ユーザーは分かりやすい日本語のエラーメッセージとともに入力した内容をフォームで確認し、修正できるようになりました。
おわりに
今回の記事では、Spring Bootアプリケーションにおいて、ユーザー入力のバリデーションを実装し、エラーメッセージを日本語で表示する方法を学びました。pom.xmlにspring-boot-starter-validationを追加し、PostFormやCommentFormといったDTOに@NotBlankや@Sizeなどのアノテーションで入力ルールを定義しました。そして、application.propertiesで設定したmessages.propertiesファイルに日本語のエラーメッセージを記述することで、ユーザーに分かりやすいフィードバックを提供できるようになりました。コントローラーでは@ValidとBindingResultを使ってエラーを検出し、必要に応じてRedirectAttributesでリダイレクト後もエラー情報を保持して画面に表示することができました。このような入力値の検証は、システムの堅牢性と使いやすさを高めるために不可欠な要素です。