8.MVC
学習目標
- コントローラーからデータベースの値を取得して画面に表示できる
- MVC(Model・View・Controller)の役割分担を、リクエストの流れとともに説明できる
- 変更の種類に応じて、どの役割を修正するかを判断できる
1. データベースからの取得
Section titled “1. データベースからの取得”リストの受け渡し の BookController は、固定の List<Book> を Model に渡します。固定リストは、コードを書き換えない限り変わりません。本のデータをデータベースで管理し、一覧をそこから取得すれば、本の登録や削除が、そのまま画面に反映されます。
1-1. コントローラーでの SELECT
Section titled “1-1. コントローラーでの SELECT”本のデータは、JDBC で作成した books テーブルにあります。固定リストを生成している箇所を、JDBC による取得に置き換えます。接続には JDBC を直接使います。
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;
@Controllerpublic 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 か所にまとめる方法は、サービスとリポジトリ で扱います。
1-2. データベースの内容の反映
Section titled “1-2. データベースの内容の反映”取得元を固定リストからデータベースに変えても、Model への格納とビュー名を返す部分、そして books.html は変わりません。Model に渡す一覧が、固定リストから books テーブルの取得結果に変わります。
サーバーを起動し直し、ブラウザーで http://localhost:8080/books を開くと、books テーブルに登録されている本の一覧が表示されます。データベースの内容が画面に反映されることを、実際に確かめます。
-
本を 1 冊追加する
データベースに接続し、次の SQL を実行します。
INSERT INTO books (title, price, stock) VALUES ('走れメロス', 396, 10); -
一覧を再読み込みする
ブラウザーで
/booksを再読み込みすると、追加した『走れメロス』が一覧に表示されます。BookControllerもbooks.htmlも変更していません。 -
追加した本を削除する
/booksを再読み込みすると、その本が一覧に表示されなくなります。DELETE FROM books WHERE title = '走れメロス';
固定リストでは、本を増減するたびにコードを書き換え、サーバーを起動し直す必要がありました。データベースから取得することで、データの変化がそのまま画面に反映され、アプリケーションを止めずに一覧を更新できます。Web アプリケーションが扱うデータの多くはデータベースにあり、画面にはその時点の内容が表示されます。
2. MVC
Section titled “2. MVC”/books の一覧表示は、役割の異なる 3 つの部分から成り立っています。本の一覧というデータ、それを表に組み立てる books.html、リクエストを受けてデータを用意する BookController です。Web アプリケーションは、この役割分担で構成されます。
2-1. Model・View・Controller
Section titled “2-1. Model・View・Controller”3 つの役割を、それぞれ Model・View・Controller と呼びます。
- Model(モデル)は、アプリケーションが扱うデータと、それを取得・加工するロジックです。ここでは本のデータ(
Bookとその一覧)と、それを取得する処理が Model にあたります。どう表示するかには依存しません。 - View(ビュー)は、データを画面に表す部分です。ここでは
books.htmlが、本の一覧を HTML の表に組み立てます。 - Controller(コントローラー)は、リクエストを受け取り、必要なデータ(Model)を用意して、どの View で表示するかを決めます。ここでは
BookControllerがその役割です。
この 3 つで画面を組み立てる構成を MVC(Model-View-Controller)と呼びます。Controller が用意した Model を View が HTML に組み立て、レスポンスとしてブラウザーに返します。
2-2. MVC に沿った構成
Section titled “2-2. MVC に沿った構成”役割を分けると、変えたいことによって修正する役割が決まり、変更の影響がその役割に収まります。
| 変えたいこと | 担当する役割 |
|---|---|
| 一覧の見た目を変える(表 → カード) | View |
| 一覧の並び順や絞り込みを変える | Model |
| ルーティングを変える | Controller |
見た目を変えるときに修正するのは、books.html(View)です。取得元を固定リストからデータベースに変えても books.html が変わらなかったのも、同じ理由です。
ただし、いまは Model が担うデータの取得や並び替えも、BookController の中にあります。そのため、一覧を価格順に並べる変更も、BookController を修正することになります。MVC では、Controller はリクエストの処理に専念し、データの用意は別のクラスに任せます。
2-3. データアクセスの分離
Section titled “2-3. データアクセスの分離”BookController はリクエストの処理に加えてデータアクセスまで担っています。このデータアクセスを、専用のクラスへ移します。
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 を呼ぶだけになります。
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;
@Controllerpublic 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 は変わりません。BookController が new BookRepository() で自分でインスタンスを作っている点と、接続情報がコードに直接書かれている点は、サービスとリポジトリ で整理します。
3. 発展: Spring MVC
Section titled “3. 発展: Spring MVC”@Controller やビュー名からテンプレートが選ばれる仕組みは、Spring が MVC パターンを実装した Web フレームワーク Spring MVC が担います。リクエストを 1 つの入口(DispatcherServlet)で受けてハンドラーへ振り分け、ViewResolver でビュー名からテンプレートを特定する、という流れは付録で扱います。