11.

Spring MVC で Model に値を入れてビューへ渡す方法|RedirectAttributes

編集
この記事の要点
  • コントローラーの引数に Model を宣言し、addAttribute() で値を入れる
  • 戻り値の Stringビュー名templates/ 配下のテンプレート名を返す
  • ビュー側では ${キー名} で参照する(Thymeleaf は th:text
  • リダイレクト先には Model の値は残らない。RedirectAttributes を使う
  • キー名を省略すると型名の先頭を小文字にした名前が使われる

基本形

@Controller
public class UserController {

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/users/{id}")
    public String show(@PathVariable Long id, Model model) {
        User user = userService.findById(id);

        model.addAttribute("user", user);
        model.addAttribute("title", "ユーザー詳細");

        return "users/show";      // templates/users/show.html
    }
}
<!-- templates/users/show.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
  <h1 th:text="${title}">タイトル</h1>
  <p th:text="${user.name}">名前</p>
  <p th:text="|${user.name} さん(${user.age}歳)|"></p>
</body>
</html>

Model引数に書くだけで Spring が渡してくれます。自分で new する必要はありません。

値の入れ方

// キーを指定する
model.addAttribute("user", user);

// キーを省略すると型名から自動で決まる
model.addAttribute(user);                 // キーは "user"
model.addAttribute(new ArrayList<User>()); // キーは "userList"

// まとめて入れる
Map<String, Object> attrs = Map.of("a", 1, "b", 2);
model.addAllAttributes(attrs);

// 確認・削除
model.containsAttribute("user");
model.asMap().remove("user");

自動で決まるキー名は分かりにくいので、明示的に文字列で指定するほうが読みやすくなります。

一覧を渡す

@GetMapping("/users")
public String index(@RequestParam(defaultValue = "1") int page, Model model) {
    List<User> users = userService.findAll(page);

    model.addAttribute("users", users);
    model.addAttribute("page", page);
    model.addAttribute("hasNext", users.size() == 20);

    return "users/index";
}
<table>
  <tr th:each="u, stat : ${users}">
    <td th:text="${stat.count}">1</td>
    <td th:text="${u.name}">名前</td>
    <td><a th:href="@{/users/{id}(id=${u.id})}">詳細</a></td>
  </tr>
</table>

<p th:if="${#lists.isEmpty(users)}">該当がありません</p>
<a th:if="${hasNext}" th:href="@{/users(page=${page + 1})}">次へ</a>

Model / ModelMap / ModelAndView

受け取り方ビュー名の指定使いどころ
Model引数戻り値の String基本はこれ
ModelMap引数戻り値の StringMap として扱いたいとき
ModelAndView自分で newオブジェクトの中で指定条件でビューを切り替えるとき
@GetMapping("/users/{id}")
public ModelAndView show(@PathVariable Long id) {
    ModelAndView mav = new ModelAndView();

    User user = userService.findById(id);
    if (user == null) {
        mav.setViewName("error/notfound");
        return mav;
    }

    mav.addObject("user", user);
    mav.setViewName("users/show");
    return mav;
}

違いの詳細は ModelとModelAndViewの違い を参照してください。

リダイレクトすると Model は消える

@PostMapping("/users")
public String create(@Validated UserForm form, BindingResult result,
                     Model model, RedirectAttributes redirect) {

    if (result.hasErrors()) {
        model.addAttribute("form", form);      // 同じリクエスト内なので届く
        return "users/new";
    }

    User saved = userService.save(form);

    // これは届かない(別のリクエストになるため)
    // model.addAttribute("message", "登録しました");

    // フラッシュ属性なら 1 回だけ次の画面に届く
    redirect.addFlashAttribute("message", "登録しました");

    // URL のクエリとして渡す(画面をブックマークできる)
    redirect.addAttribute("id", saved.getId());

    return "redirect:/users/{id}";     // /users/1 に転送される
}
メソッドどう渡るかURL に出るか
addFlashAttribute()セッション経由で 1 回だけ出ない
addAttribute()クエリ文字列 / パス変数出る

「登録しました」のような一度きりのメッセージはフラッシュ属性、再表示できるべき条件(検索語・ページ番号)は URL に載せる、と使い分けます。

全画面に共通の値を渡す

@ControllerAdvice
public class GlobalModelAdvice {

    @ModelAttribute("appName")
    public String appName() {
        return "サンプルアプリ";
    }

    @ModelAttribute("loginUser")
    public User loginUser(HttpSession session) {
        return (User) session.getAttribute("user");
    }
}

@ControllerAdvice@ModelAttribute を書くと、すべてのコントローラーの Model に自動で追加されます。ヘッダーに出すログインユーザー名などに使えます。

REST API では Model を使わない

@RestController          // @Controller + @ResponseBody
@RequestMapping("/api/users")
public class UserApiController {

    @GetMapping("/{id}")
    public User show(@PathVariable Long id) {
        return userService.findById(id);      // JSON になって返る
    }

    @GetMapping
    public ResponseEntity<List<User>> index() {
        return ResponseEntity.ok(userService.findAll());
    }
}

@RestController では戻り値がそのまま JSON に変換されます。ビュー名としては解釈されません。

@ModelAttribute でフォームと結び付ける

public class UserForm {
    @NotBlank(message = "名前は必須です")
    private String name;

    @Min(value = 0, message = "0 以上を入力してください")
    private int age;

    // ゲッター・セッターが必要(Thymeleaf と Spring がこれを使う)
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}
@Controller
@RequestMapping("/users")
public class UserController {

    // 入力画面: 空のフォームを Model に入れる
    @GetMapping("/new")
    public String newForm(Model model) {
        model.addAttribute("userForm", new UserForm());
        return "users/new";
    }

    // 登録: 引数に書くだけで Model にも自動で入る
    @PostMapping
    public String create(@Validated @ModelAttribute UserForm userForm,
                         BindingResult result,
                         RedirectAttributes redirect) {

        if (result.hasErrors()) {
            return "users/new";     // userForm は Model に入ったままなので再表示できる
        }

        userService.save(userForm);
        redirect.addFlashAttribute("message", "登録しました");
        return "redirect:/users";
    }
}

@ModelAttribute を付けた引数は、自動的に Model にも追加されます。キー名は型名の先頭を小文字にしたもの(UserFormuserForm)です。エラーで入力画面に戻したとき、入力値がそのまま残るのはこの仕組みによります。

<form th:action="@{/users}" th:object="${userForm}" method="post">
  <input type="text" th:field="*{name}">
  <span th:if="${#fields.hasErrors('name')}" th:errors="*{name}"></span>

  <input type="number" th:field="*{age}">
  <button type="submit">登録</button>
</form>

th:fieldid name value を自動で埋めます。BindingResult は検証したい引数の直後に置く必要があります。間に別の引数を挟むと例外になります。

値の変換とフォーマット

public class SearchForm {
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    private LocalDate from;

    @NumberFormat(pattern = "#,###")
    private Integer amount;
}
<!-- 表示側でも整形できる -->
<span th:text="${#temporals.format(user.createdAt, 'yyyy年M月d日')}"></span>
<span th:text="${#numbers.formatInteger(price, 1, 'COMMA')}"></span>

@DateTimeFormat が無いと、2026-09-07 のような文字列を LocalDate に変換できず Failed to convert エラーになります。

うまく表示されないとき

症状原因
画面に値が出ないキー名の綴り違い。${user.name}user が Model のキーと一致しているか
Whitelabel Error Pageビューが見つからない。templates/ 配下のパスを確認する
ビュー名がそのまま表示される@RestController になっている。@Controller にする
EL1007E: Property or field 'name' cannot be found on nullModel に入れていない/値が nullth:if で囲む
ゲッターが無くて読めないThymeleaf はゲッター経由で読む。record かゲッターを用意する
リダイレクト後に値が消えるRedirectAttributes を使う

関連

編集
Post Share
子ページ

子ページはありません

同階層のページ
  1. インストール(eclipseプラグイン)
  2. クイックスタート
  3. プロジェクトの作成
  4. Spring Bootプロジェクトの作成
  5. Spring Bootプロジェクトの実行
  6. Spring BootでHello World!
  7. アノテーション一覧
  8. DB接続設定からエンティティおよびリポジトリの作成、値の取得まで(JPA編)
  9. DB接続設定や値の取得(JdbcTemplate編)
  10. ビューから値をモデルに格納しコントローラーで受け取る方法
  11. コントローラーにてモデルに値を格納してビューに渡す方法
  12. テンプレートエンジン
  13. ModelとModelAndViewの違い
  14. AOPの使用方法
  15. classpath: 内部ファイルの読み込み
  16. file: 外部ファイルの読み込み
  17. CSVファイルアップロード方法(Ajax)
  18. CSVファイルダウンロード方法(Ajax)
  19. Spring Bootプロジェクトのビルドと本番環境へのデプロイ方法(内部tomcat使用)
  20. Application.propertiesの環境依存設定の分割方法
  21. JPAにおけるEntityManagerの取得方法
  22. JPAにおけるjava.sql.Connectionの取得方法
  23. エラー一覧
  24. jarの引数を受け取る方法
  25. Spring BootでGmailからメール送信
  26. 複数のDBに接続する設定(Spring Boot & JPA編)
  27. ポート番号の変更
  28. Basic認証の実装と特定のURLに限定する方法
  29. Spring SecurityのBasic認証の無効化
  30. 独自のエラーページを定義する方法
  31. プロパティファイルの値やjar実行時の引数を取得する方法

最近更新/作成されたページ