@ModelAttributeとmodel.attributeの違い
一旦書き留め
問題
下記コードのUser.javaをEclipseのリファクタリング機能で参照ごと名前を変えたところエラーが表示されるようになった。
@Controller
public class HomeController {
@Autowired
private AppUserService userService;
@PostMapping("/register")
public String registerUser(@ModelAttribute User user) {
userService.saveUser(user);
return "registerSuccess";
}
@GetMapping("/form")
public String readForm(@ModelAttribute User user) {
return "form";
}
@PostMapping("/form")
public String confirm(@ModelAttribute User user, @RequestParam String action) {
if ("register".equals(action)) {
// 新規登録
return "register";
} else if ("login".equals(action)) {
// ログイン処理
return "loginSuccess";
} else if ("showTable".equals(action)) {
// 全件表示
return "showTable";
}
return "confirm";
}
}
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotEmpty
private String password;
@NotEmpty
private String confirmPassword;
@NotEmpty
private String username;
@Email
@NotEmpty
private String email;
@NotEmpty
private String firstname;
@NotEmpty
private String lastname;
// getterとsetterは省略
}
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
<div class="main" style="text-align:center;">
<form th:action="@{/form}" th:object="${user}" method="post">
<label for="username">名前 : </label>
<input id="username" type="text" th:field="*{username}"><br>
<label for="email">E-Mail : </label>
<input id="email" type="email" th:field="*{email}"><br>
<button type="submit" name="action" value="login">ログイン</button>
<button type="submit" name="action" value="register">新規登録</button>
<button type="submit" name="action" value="showTable">全件表示</button>
</form>
</div>
</body>
</html>
エラー抜粋
Whitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Wed Dec 20 15:53:31 JST 2023
There was an unexpected error (type=Internal Server Error, status=500).
An error happened during template parsing (template: "class path resource [templates/form.html]")
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/form.html]")
Caused by: org.attoparser.ParseException: Error during execution of processor 'org.thymeleaf.spring6.processor.SpringInputGeneralFieldTagProcessor' (template: "form" - line 11, col 36)
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Error during execution of processor 'org.thymeleaf.spring6.processor.SpringInputGeneralFieldTagProcessor' (template: "form" - line 11, col 36)
Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'user' available as request attribute
[2m2023-12-20T15:53:31.558+09:00[0;39m [31mERROR[0;39m [35m25424[0;39m [2m---[0;39m [2m[nio-8080-exec-1][0;39m [2m[0;39m[36mo.a.c.c.C.[.[.[/].[dispatcherServlet] [0;39m [2m:[0;39m Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed: org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/form.html]")] with root cause
java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'user' available as request attribute
回避方法
model.attributeを使う。
@Controller
public class HomeController {
@Autowired
private AppUserService userService;
@PostMapping("/register")
public String registerUser(@ModelAttribute AppUser user) {
userService.saveUser(user);
return "registerSuccess";
}
@GetMapping("/form")
public String readForm(Model model) {
model.addAttribute("user", new AppUser());
return "form";
}
@PostMapping("/form")
public String confirm(Model model, @RequestParam String action) {
model.addAttribute("user", new AppUser());
if ("register".equals(action)) {
// 新規登録
return "register";
} else if ("login".equals(action)) {
// ログイン処理
return "loginSuccess";
} else if ("showTable".equals(action)) {
// 全件表示
return "showTable";
}
return "confirm";
}
}
記述方法
@ModelAttribute
@GetMapping("/form")
public String readForm(@ModelAttribute AppUser user) {
return "form";
}
ChatGPT4引用
この方法では、AppUser型のuserオブジェクトがリクエストから自動的にバインドされます。リクエストにuserのデータが含まれていない場合(例えば、フォームが初めて表示される場合)、SpringはAppUserの空のインスタンスを作成し、それをモデルにuserという名前で追加します。しかし、この挙動はSpringのバージョンや設定によって異なる場合があります。
model.attribute
@GetMapping("/form")
public String readForm(Model model) {
model.addAttribute("user", new AppUser());
return "form";
}
ChatGPT4引用
この方法では、Modelオブジェクトを直接操作して、userという名前でAppUserの新しいインスタンスをモデルに追加します。これはより明示的で、どのような状況でもuserオブジェクトがモデルに存在することが保証されます。特に、リクエストパラメータが関与しない新規フォームの表示などではこの方法が推奨されます。
どちらを使うべきか
・一般的に、新規フォームの表示のようなシンプルなケースでは、Modelを使用してモデル属性を明示的に追加する方法が推奨されます。これにより、任意の名前でオブジェクトをモデルに追加し、テンプレートの期待通りに動作することが保証されます。
・@ModelAttributeを使用する方法は、フォームの送信結果を処理する場合など、リクエストパラメータをバインドする必要がある場合に適しています。
Discussion