📑

@ModelAttributeとmodel.attributeの違い

2023/12/20に公開

一旦書き留め

問題

下記コードのUser.javaをEclipseのリファクタリング機能で参照ごと名前を変えたところエラーが表示されるようになった。

HomeController.java
@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";

 }
 
}
User.java
@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は省略
}
form.html
<!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を使う。

HomeController.java
@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