Skip to content
Playground

8.MVC

学習目標

  • コントローラーからデータベースの値を取得して画面に表示できる
  • MVC(Model・View・Controller)の役割分担を、リクエストの流れとともに説明できる
  • 変更の種類に応じて、どの役割を修正するかを判断できる

リストの受け渡しBookController は、固定の List<Book>Model に渡します。固定リストは、コードを書き換えない限り変わりません。本のデータをデータベースで管理し、一覧をそこから取得すれば、本の登録や削除が、そのまま画面に反映されます。

本のデータは、JDBC で作成した books テーブルにあります。固定リストを生成している箇所を、JDBC による取得に置き換えます。接続には JDBC を直接使います。

BookController.java
package com.example.bookstore;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BookController {
@GetMapping("/books")
public String books(Model model) {
List<Book> books = new ArrayList<>();
String url = "jdbc:mysql://127.0.0.1:3307/bookstore";
String sql = "SELECT title, price, stock FROM books";
try (Connection conn = DriverManager.getConnection(url, "root", "root");
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
books.add(new Book(rs.getString("title"), rs.getInt("price"), rs.getInt("stock")));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
model.addAttribute("books", books);
return "books";
}
}

接続情報(URL・ユーザー・パスワード)は、メソッドの中に直接書いています。接続情報を 1 か所にまとめる方法は、サービスとリポジトリ で扱います。

取得元を固定リストからデータベースに変えても、Model への格納とビュー名を返す部分、そして books.html は変わりません。Model に渡す一覧が、固定リストから books テーブルの取得結果に変わります。

サーバーを起動し直し、ブラウザーで http://localhost:8080/books を開くと、books テーブルに登録されている本の一覧が表示されます。データベースの内容が画面に反映されることを、実際に確かめます。

  1. 本を 1 冊追加する

    データベースに接続し、次の SQL を実行します。

    INSERT INTO books (title, price, stock) VALUES ('走れメロス', 396, 10);
  2. 一覧を再読み込みする

    ブラウザーで /books を再読み込みすると、追加した『走れメロス』が一覧に表示されます。BookControllerbooks.html も変更していません。

  3. 追加した本を削除する

    /books を再読み込みすると、その本が一覧に表示されなくなります。

    DELETE FROM books WHERE title = '走れメロス';

固定リストでは、本を増減するたびにコードを書き換え、サーバーを起動し直す必要がありました。データベースから取得することで、データの変化がそのまま画面に反映され、アプリケーションを止めずに一覧を更新できます。Web アプリケーションが扱うデータの多くはデータベースにあり、画面にはその時点の内容が表示されます。

/books の一覧表示は、役割の異なる 3 つの部分から成り立っています。本の一覧というデータ、それを表に組み立てる books.html、リクエストを受けてデータを用意する BookController です。Web アプリケーションは、この役割分担で構成されます。

3 つの役割を、それぞれ ModelViewController と呼びます。

  • Model(モデル)は、アプリケーションが扱うデータと、それを取得・加工するロジックです。ここでは本のデータ(Book とその一覧)と、それを取得する処理が Model にあたります。どう表示するかには依存しません。
  • View(ビュー)は、データを画面に表す部分です。ここでは books.html が、本の一覧を HTML の表に組み立てます。
  • Controller(コントローラー)は、リクエストを受け取り、必要なデータ(Model)を用意して、どの View で表示するかを決めます。ここでは BookController がその役割です。

この 3 つで画面を組み立てる構成を MVC(Model-View-Controller)と呼びます。Controller が用意した Model を View が HTML に組み立て、レスポンスとしてブラウザーに返します。

役割を分けると、変えたいことによって修正する役割が決まり、変更の影響がその役割に収まります。

変えたいこと担当する役割
一覧の見た目を変える(表 → カード)View
一覧の並び順や絞り込みを変えるModel
ルーティングを変えるController

見た目を変えるときに修正するのは、books.html(View)です。取得元を固定リストからデータベースに変えても books.html が変わらなかったのも、同じ理由です。

ただし、いまは Model が担うデータの取得や並び替えも、BookController の中にあります。そのため、一覧を価格順に並べる変更も、BookController を修正することになります。MVC では、Controller はリクエストの処理に専念し、データの用意は別のクラスに任せます。

BookController はリクエストの処理に加えてデータアクセスまで担っています。このデータアクセスを、専用のクラスへ移します。

BookRepository.java
package com.example.bookstore;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
public class BookRepository {
public List<Book> findAll() {
List<Book> books = new ArrayList<>();
String url = "jdbc:mysql://127.0.0.1:3307/bookstore";
String sql = "SELECT title, price, stock FROM books";
try (Connection conn = DriverManager.getConnection(url, "root", "root");
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
books.add(new Book(rs.getString("title"), rs.getInt("price"), rs.getInt("stock")));
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
return books;
}
}

SQL や接続といったデータアクセスは、BookRepository の中にまとまりました。

BookController は JDBC を手放し、BookRepository を呼ぶだけになります。

BookController.java
package com.example.bookstore;
import java.util.List;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class BookController {
private final BookRepository bookRepository = new BookRepository();
@GetMapping("/books")
public String books(Model model) {
List<Book> books = bookRepository.findAll();
model.addAttribute("books", books);
return "books";
}
}

データの取得方法を変えても BookController は変わらず、画面を変えても BookRepository は変わりません。BookControllernew BookRepository() で自分でインスタンスを作っている点と、接続情報がコードに直接書かれている点は、サービスとリポジトリ で整理します。

@Controller やビュー名からテンプレートが選ばれる仕組みは、Spring が MVC パターンを実装した Web フレームワーク Spring MVC が担います。リクエストを 1 つの入口(DispatcherServlet)で受けてハンドラーへ振り分け、ViewResolver でビュー名からテンプレートを特定する、という流れは付録で扱います。