はじめに
Servlet/JSP入門の第6回は MVCパターン(Servlet + JSP) です。
これまでの学習で、Servletで処理を書きJSPで画面を表示する方法を学びました。しかし、実際のアプリケーション開発では、コードをきちんと役割ごとに分離しないと、保守が困難になります。今回は、Web開発の定番設計パターンである MVC を学びましょう。
第6回で学ぶこと
- ロジックと画面表示を混在させる問題点
- MVCパターンの概念
- Servlet(Controller)、JSP(View)、JavaBean(Model)の役割
- RequestDispatcher によるフォワード
- request.setAttribute() によるデータの受け渡し
- JavaBeanの規約
- MVCパターンで構築する社員一覧アプリ
1. ロジックと画面表示を混在させる問題
悪い例:すべてServletに書く
@WebServlet("/employee-bad")
public class BadExampleServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
// データ取得(本来はDB処理)
String[] names = {"田中太郎", "鈴木花子", "佐藤次郎"};
int[] ages = {25, 30, 28};
// HTML出力(画面表示)
out.println("<!DOCTYPE html>");
out.println("<html><head><title>社員一覧</title></head>");
out.println("<body>");
out.println("<h1>社員一覧</h1>");
out.println("<table border='1'>");
out.println("<tr><th>名前</th><th>年齢</th></tr>");
for (int i = 0; i < names.length; i++) {
out.println("<tr><td>" + names[i] + "</td><td>" + ages[i] + "</td></tr>");
}
out.println("</table>");
out.println("</body></html>");
}
}
何が問題なのか?
| 問題点 | 具体的な影響 |
|---|---|
| 可読性が低い | JavaコードとHTMLが混在して読みにくい |
| 保守性が低い | デザイン変更のたびにJavaコードを修正 |
| 再利用できない | データ取得ロジックが画面出力と結合 |
| 分業できない | デザイナーとプログラマーが同じファイルを編集 |
| テストしにくい | ロジック単体のテストが困難 |
悪い例:すべてJSPに書く
<%@ page import="java.sql.*" %>
<%
// DBからデータ取得(ロジック)
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/mydb", "user", "pass");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM employees");
%>
<html>
<body>
<table>
<% while (rs.next()) { %>
<tr><td><%= rs.getString("name") %></td></tr>
<% } %>
</table>
<%
rs.close();
stmt.close();
conn.close();
%>
</body>
</html>
JSPにロジックを書くのも同じ問題を抱えています。画面表示を担当するJSPにDB処理を書くべきではありません。
2. MVCパターンとは
MVC = Model + View + Controller
MVCパターン は、アプリケーションを3つの役割に分離する設計パターンです。
┌──────────┐ リクエスト ┌──────────────────┐
│ ブラウザ │ ─────────────→ │ Controller │
│ │ │ (Servlet) │
│ │ │ ・リクエスト受付 │
│ │ │ ・Modelに処理依頼 │
│ │ │ ・Viewに表示依頼 │
│ │ └──┬───── ─┬────────┘
│ │ │ │
│ │ ②処理依頼│ │③表示依頼
│ │ ▼ │ (forward)
│ │ ┌──────────┐ │
│ │ │ Model │ │
│ │ │ (JavaBean)│ │
│ │ │ ・データ │ │
│ │ │ ・ロジック │ │
│ │ └──────────┘ │
│ │ │ │
│ │ ①データ取得│ ▼
│ │ │ ┌──────────┐
│ │ レスポンス │ │ View │
│ │ ←────────────────────── │ (JSP) │
│ │ │ ・画面表示 │
└──────────┘ └──────────┘
各層の役割
| 要素 | 実装技術 | 役割 |
|---|---|---|
| Model | JavaBean(POJO) | データの保持、ビジネスロジック |
| View | JSP | 画面の表示(HTMLの生成) |
| Controller | Servlet | リクエストの受付、ModelとViewの橋渡し |
MVCのメリット
| メリット | 説明 |
|---|---|
| 分業が可能 | デザイナー(View)とプログラマー(Model/Controller)で分担可能 |
| 保守性が高い | 各層が独立しているため、変更の影響範囲が限定的 |
| テストしやすい | Model(ロジック)を単体テスト可能 |
| 再利用性が高い | 同じModelを別のViewで使い回せる |
3. JavaBeanの規約
JavaBean(POJO)とは?
MVCの Model として使うJavaクラスです。データを保持するための規約に従ったクラスのことを指します。
JavaBeanの規約
package model;
import java.io.Serializable;
public class Employee implements Serializable {
// ① フィールドはprivate
private int id;
private String name;
private String department;
private int age;
// ② 引数なしのコンストラクタ(デフォルトコンストラクタ)
public Employee() {
}
// 引数ありのコンストラクタ(任意)
public Employee(int id, String name, String department, int age) {
this.id = id;
this.name = name;
this.department = department;
this.age = age;
}
// ③ getter / setter メソッド
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDepartment() {
return department;
}
public void setDepartment(String department) {
this.department = department;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
JavaBeanの3つの規約
| 規約 | 説明 |
|---|---|
フィールドは private
|
外部から直接アクセスさせない(カプセル化) |
| 引数なしコンストラクタ | フレームワーク等がインスタンスを生成するために必要 |
| getter / setter |
getXxx() / setXxx() の命名規則に従う |
補足: Serializable インターフェースの実装は必須ではありませんが、セッションに保存する場合や将来の拡張のために実装しておくのが一般的です。
4. RequestDispatcher と request.setAttribute()
Controller から View へのデータ受け渡し
MVCパターンでは、Controller(Servlet)で処理したデータを View(JSP)に渡す必要があります。
// ① データをリクエストスコープにセット
request.setAttribute("キー名", 値);
// ② JSPにフォワード
RequestDispatcher dispatcher = request.getRequestDispatcher("/WEB-INF/view.jsp");
dispatcher.forward(request, response);
JSP側でデータを受け取る
<%-- request.getAttribute() でデータを取得 --%>
<%= request.getAttribute("キー名") %>
データの流れ
Controller (Servlet) View (JSP)
───────────────── ──────────
request.setAttribute("emp", emp); → request.getAttribute("emp")
request.setAttribute("list", list); → request.getAttribute("list")
│
▼
forward(request, response)
注意点
-
setAttribute()で保存できるのは オブジェクト(Object型) です - JSPで取得する際は キャスト が必要です
<%
Employee emp = (Employee) request.getAttribute("emp");
@SuppressWarnings("unchecked")
List<Employee> list = (List<Employee>) request.getAttribute("empList");
%>
5. MVCパターンの実践:社員一覧アプリ
プロジェクト構成
WebStudy/
├── src/main/java/
│ ├── model/
│ │ └── Employee.java ← Model(JavaBean)
│ ├── logic/
│ │ └── EmployeeLogic.java ← ビジネスロジック
│ └── servlet/
│ ├── EmployeeListServlet.java ← Controller
│ └── EmployeeDetailServlet.java ← Controller
└── src/main/webapp/
└── WEB-INF/
└── view/
├── employee-list.jsp ← View
└── employee-detail.jsp ← View
Model:Employee.java
package model;
import java.io.Serializable;
public class Employee implements Serializable {
private int id;
private String name;
private String department;
private int age;
private String email;
public Employee() {
}
public Employee(int id, String name, String department, int age, String email) {
this.id = id;
this.name = name;
this.department = department;
this.age = age;
this.email = email;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
ビジネスロジック:EmployeeLogic.java
package logic;
import java.util.ArrayList;
import java.util.List;
import model.Employee;
public class EmployeeLogic {
// 仮のデータ(実際にはDBから取得する)
private static final List<Employee> EMPLOYEE_LIST = new ArrayList<>();
static {
EMPLOYEE_LIST.add(new Employee(1, "田中太郎", "開発部", 25, "tanaka@example.com"));
EMPLOYEE_LIST.add(new Employee(2, "鈴木花子", "営業部", 30, "suzuki@example.com"));
EMPLOYEE_LIST.add(new Employee(3, "佐藤次郎", "人事部", 28, "sato@example.com"));
EMPLOYEE_LIST.add(new Employee(4, "高橋美咲", "開発部", 24, "takahashi@example.com"));
EMPLOYEE_LIST.add(new Employee(5, "伊藤健一", "営業部", 35, "ito@example.com"));
}
/**
* 社員一覧を取得する
*/
public List<Employee> findAll() {
return new ArrayList<>(EMPLOYEE_LIST);
}
/**
* IDで社員を検索する
*/
public Employee findById(int id) {
return EMPLOYEE_LIST.stream()
.filter(e -> e.getId() == id)
.findFirst()
.orElse(null);
}
/**
* 部署で社員を絞り込む
*/
public List<Employee> findByDepartment(String department) {
List<Employee> result = new ArrayList<>();
for (Employee emp : EMPLOYEE_LIST) {
if (emp.getDepartment().equals(department)) {
result.add(emp);
}
}
return result;
}
}
Controller:EmployeeListServlet.java
package servlet;
import java.io.IOException;
import java.util.List;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import logic.EmployeeLogic;
import model.Employee;
@WebServlet("/employee/list")
public class EmployeeListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
// ① ビジネスロジックの実行
EmployeeLogic logic = new EmployeeLogic();
String department = request.getParameter("department");
List<Employee> empList;
if (department != null && !department.isEmpty()) {
empList = logic.findByDepartment(department);
} else {
empList = logic.findAll();
}
// ② データをリクエストスコープに保存
request.setAttribute("empList", empList);
request.setAttribute("selectedDept", department);
// ③ JSP(View)にフォワード
request.getRequestDispatcher("/WEB-INF/view/employee-list.jsp")
.forward(request, response);
}
}
Controller:EmployeeDetailServlet.java
package servlet;
import java.io.IOException;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import logic.EmployeeLogic;
import model.Employee;
@WebServlet("/employee/detail")
public class EmployeeDetailServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String idStr = request.getParameter("id");
if (idStr == null) {
response.sendRedirect(request.getContextPath() + "/employee/list");
return;
}
try {
int id = Integer.parseInt(idStr);
// ① ビジネスロジックの実行
EmployeeLogic logic = new EmployeeLogic();
Employee emp = logic.findById(id);
if (emp == null) {
request.setAttribute("error", "指定された社員が見つかりません(ID: " + id + ")");
request.getRequestDispatcher("/WEB-INF/view/employee-list.jsp")
.forward(request, response);
return;
}
// ② データをリクエストスコープに保存
request.setAttribute("employee", emp);
// ③ JSPにフォワード
request.getRequestDispatcher("/WEB-INF/view/employee-detail.jsp")
.forward(request, response);
} catch (NumberFormatException e) {
response.sendRedirect(request.getContextPath() + "/employee/list");
}
}
}
View:employee-list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List, model.Employee" %>
<!DOCTYPE html>
<html>
<head>
<title>社員一覧</title>
<style>
body { font-family: sans-serif; margin: 20px; }
table { border-collapse: collapse; width: 100%; max-width: 800px; }
th, td { border: 1px solid #ddd; padding: 10px; text-align: left; }
th { background-color: #336699; color: white; }
tr:nth-child(even) { background-color: #f9f9f9; }
tr:hover { background-color: #e9e9e9; }
a { color: #336699; }
.filter { margin-bottom: 15px; }
.error { color: red; }
</style>
</head>
<body>
<h1>社員一覧</h1>
<% if (request.getAttribute("error") != null) { %>
<p class="error"><%= request.getAttribute("error") %></p>
<% } %>
<%-- 部署フィルター --%>
<div class="filter">
<form action="<%= request.getContextPath() %>/employee/list" method="get">
<label>部署で絞り込み:</label>
<select name="department">
<option value="">すべて</option>
<%
String selectedDept = (String) request.getAttribute("selectedDept");
%>
<option value="開発部" <%= "開発部".equals(selectedDept) ? "selected" : "" %>>開発部</option>
<option value="営業部" <%= "営業部".equals(selectedDept) ? "selected" : "" %>>営業部</option>
<option value="人事部" <%= "人事部".equals(selectedDept) ? "selected" : "" %>>人事部</option>
</select>
<button type="submit">絞り込み</button>
</form>
</div>
<%-- 社員テーブル --%>
<%
@SuppressWarnings("unchecked")
List<Employee> empList = (List<Employee>) request.getAttribute("empList");
%>
<% if (empList != null && !empList.isEmpty()) { %>
<table>
<tr>
<th>ID</th>
<th>名前</th>
<th>部署</th>
<th>年齢</th>
<th>詳細</th>
</tr>
<% for (Employee emp : empList) { %>
<tr>
<td><%= emp.getId() %></td>
<td><%= emp.getName() %></td>
<td><%= emp.getDepartment() %></td>
<td><%= emp.getAge() %></td>
<td><a href="<%= request.getContextPath() %>/employee/detail?id=<%= emp.getId() %>">詳細</a></td>
</tr>
<% } %>
</table>
<p>全 <%= empList.size() %> 件</p>
<% } else { %>
<p>該当する社員がいません。</p>
<% } %>
</body>
</html>
View:employee-detail.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="model.Employee" %>
<!DOCTYPE html>
<html>
<head>
<title>社員詳細</title>
<style>
body { font-family: sans-serif; margin: 20px; }
table { border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 10px; }
th { background-color: #336699; color: white; text-align: left; width: 120px; }
a { color: #336699; }
</style>
</head>
<body>
<%
Employee emp = (Employee) request.getAttribute("employee");
%>
<h1>社員詳細</h1>
<table>
<tr><th>ID</th><td><%= emp.getId() %></td></tr>
<tr><th>名前</th><td><%= emp.getName() %></td></tr>
<tr><th>部署</th><td><%= emp.getDepartment() %></td></tr>
<tr><th>年齢</th><td><%= emp.getAge() %> 歳</td></tr>
<tr><th>メール</th><td><%= emp.getEmail() %></td></tr>
</table>
<br />
<a href="<%= request.getContextPath() %>/employee/list">一覧に戻る</a>
</body>
</html>
MVCの処理フロー(まとめ)
1. ブラウザが /employee/list にアクセス(GETリクエスト)
2. EmployeeListServlet(Controller)が受け取る
→ EmployeeLogic を呼び出してデータを取得
→ request.setAttribute() でデータをセット
→ employee-list.jsp に forward
3. employee-list.jsp(View)がHTMLを生成
→ request.getAttribute() でデータを取得
→ HTMLテーブルとして表示
4. ブラウザにHTMLが返される
6. MVCパターンの設計指針
各層に書くべきこと・書くべきでないこと
| 層 | 書くべきこと | 書くべきでないこと |
|---|---|---|
| Model | データ保持、ビジネスロジック、DB操作 | HTML出力、リクエスト処理 |
| View | HTML出力、データの表示 | ビジネスロジック、DB操作 |
| Controller | リクエスト受付、Model呼び出し、View選択 | HTML出力、ビジネスロジック |
Controllerの処理パターン
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// ① リクエストパラメータの取得
String param = request.getParameter("key");
// ② Model(ビジネスロジック)の実行
SomeLogic logic = new SomeLogic();
SomeData data = logic.process(param);
// ③ 結果をリクエストスコープに保存
request.setAttribute("data", data);
// ④ View(JSP)にフォワード
request.getRequestDispatcher("/WEB-INF/view/result.jsp")
.forward(request, response);
}
この4ステップがControllerの基本パターンです。
練習問題
問題1:書籍一覧アプリ ⭐
MVCパターンで書籍一覧を表示するアプリを作成してください。
- Model:
Book.java(id, title, author, price) - Logic:
BookLogic.java(3冊以上のダミーデータを返す) - Controller:
BookListServlet.java(URL:/book/list) - View:
book-list.jsp(テーブルで一覧表示)
模範解答
model/Book.java
package model;
import java.io.Serializable;
public class Book implements Serializable {
private int id;
private String title;
private String author;
private int price;
public Book() {
}
public Book(int id, String title, String author, int price) {
this.id = id;
this.title = title;
this.author = author;
this.price = price;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public String getAuthor() { return author; }
public void setAuthor(String author) { this.author = author; }
public int getPrice() { return price; }
public void setPrice(int price) { this.price = price; }
}
logic/BookLogic.java
package logic;
import java.util.ArrayList;
import java.util.List;
import model.Book;
public class BookLogic {
public List<Book> findAll() {
List<Book> books = new ArrayList<>();
books.add(new Book(1, "Java入門", "山田太郎", 2800));
books.add(new Book(2, "Servlet/JSP実践", "鈴木花子", 3200));
books.add(new Book(3, "データベース入門", "佐藤次郎", 2500));
books.add(new Book(4, "HTML/CSS基礎", "高橋美咲", 1800));
return books;
}
}
servlet/BookListServlet.java
package servlet;
import java.io.IOException;
import java.util.List;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import logic.BookLogic;
import model.Book;
@WebServlet("/book/list")
public class BookListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
BookLogic logic = new BookLogic();
List<Book> bookList = logic.findAll();
request.setAttribute("bookList", bookList);
request.getRequestDispatcher("/WEB-INF/view/book-list.jsp")
.forward(request, response);
}
}
WEB-INF/view/book-list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List, model.Book" %>
<!DOCTYPE html>
<html>
<head>
<title>書籍一覧</title>
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; }
th { background-color: #336699; color: white; }
</style>
</head>
<body>
<h1>書籍一覧</h1>
<%
@SuppressWarnings("unchecked")
List<Book> bookList = (List<Book>) request.getAttribute("bookList");
%>
<table>
<tr><th>ID</th><th>タイトル</th><th>著者</th><th>価格</th></tr>
<% for (Book book : bookList) { %>
<tr>
<td><%= book.getId() %></td>
<td><%= book.getTitle() %></td>
<td><%= book.getAuthor() %></td>
<td><%= String.format("%,d", book.getPrice()) %> 円</td>
</tr>
<% } %>
</table>
</body>
</html>
問題2:商品検索アプリ ⭐⭐
MVCパターンで、商品名で検索できるアプリを作成してください。
- Model:
Product.java(id, name, category, price) - Logic:
ProductLogic.java(全件取得と名前検索メソッド) - Controller:
ProductSearchServlet.java(GETで検索フォームと結果を表示) - View:
product-search.jsp(検索フォームと結果テーブルを1画面で表示)
模範解答
model/Product.java
package model;
import java.io.Serializable;
public class Product implements Serializable {
private int id;
private String name;
private String category;
private int price;
public Product() {
}
public Product(int id, String name, String category, int price) {
this.id = id;
this.name = name;
this.category = category;
this.price = price;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCategory() { return category; }
public void setCategory(String category) { this.category = category; }
public int getPrice() { return price; }
public void setPrice(int price) { this.price = price; }
}
logic/ProductLogic.java
package logic;
import java.util.ArrayList;
import java.util.List;
import model.Product;
public class ProductLogic {
private static final List<Product> PRODUCTS = new ArrayList<>();
static {
PRODUCTS.add(new Product(1, "ノートパソコン", "電子機器", 89800));
PRODUCTS.add(new Product(2, "ワイヤレスマウス", "電子機器", 3200));
PRODUCTS.add(new Product(3, "Java入門書", "書籍", 2800));
PRODUCTS.add(new Product(4, "USBメモリ 64GB", "電子機器", 1500));
PRODUCTS.add(new Product(5, "プログラミング入門", "書籍", 2200));
}
public List<Product> findAll() {
return new ArrayList<>(PRODUCTS);
}
public List<Product> searchByName(String keyword) {
List<Product> result = new ArrayList<>();
for (Product p : PRODUCTS) {
if (p.getName().contains(keyword)) {
result.add(p);
}
}
return result;
}
}
servlet/ProductSearchServlet.java
package servlet;
import java.io.IOException;
import java.util.List;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import logic.ProductLogic;
import model.Product;
@WebServlet("/product/search")
public class ProductSearchServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
ProductLogic logic = new ProductLogic();
String keyword = request.getParameter("keyword");
List<Product> productList;
if (keyword != null && !keyword.trim().isEmpty()) {
productList = logic.searchByName(keyword);
} else {
productList = logic.findAll();
keyword = "";
}
request.setAttribute("productList", productList);
request.setAttribute("keyword", keyword);
request.getRequestDispatcher("/WEB-INF/view/product-search.jsp")
.forward(request, response);
}
}
WEB-INF/view/product-search.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List, model.Product" %>
<!DOCTYPE html>
<html>
<head>
<title>商品検索</title>
<style>
table { border-collapse: collapse; margin-top: 10px; }
th, td { border: 1px solid #ddd; padding: 8px; }
th { background-color: #336699; color: white; }
</style>
</head>
<body>
<h1>商品検索</h1>
<%
String keyword = (String) request.getAttribute("keyword");
%>
<form action="<%= request.getContextPath() %>/product/search" method="get">
<input type="text" name="keyword" value="<%= keyword %>" placeholder="商品名を入力" />
<button type="submit">検索</button>
</form>
<%
@SuppressWarnings("unchecked")
List<Product> productList = (List<Product>) request.getAttribute("productList");
%>
<% if (!keyword.isEmpty()) { %>
<p>「<%= keyword %>」の検索結果: <%= productList.size() %> 件</p>
<% } %>
<% if (!productList.isEmpty()) { %>
<table>
<tr><th>ID</th><th>商品名</th><th>カテゴリ</th><th>価格</th></tr>
<% for (Product p : productList) { %>
<tr>
<td><%= p.getId() %></td>
<td><%= p.getName() %></td>
<td><%= p.getCategory() %></td>
<td><%= String.format("%,d", p.getPrice()) %> 円</td>
</tr>
<% } %>
</table>
<% } else { %>
<p>該当する商品がありません。</p>
<% } %>
</body>
</html>
問題3:社員登録・一覧アプリ(CRUDのCR) ⭐⭐⭐
MVCパターンで、社員の登録と一覧表示ができるアプリを作成してください。
- Model:
Staff.java(id, name, department, email) - Logic:
StaffLogic.java(全件取得、登録メソッド。データはListで保持) - Controller:
StaffListServlet.java(一覧表示)、StaffAddServlet.java(登録フォーム表示+登録処理) - View: 一覧画面と登録フォーム画面
- 登録後は一覧画面にリダイレクト
模範解答
model/Staff.java
package model;
import java.io.Serializable;
public class Staff implements Serializable {
private int id;
private String name;
private String department;
private String email;
public Staff() {
}
public Staff(int id, String name, String department, String email) {
this.id = id;
this.name = name;
this.department = department;
this.email = email;
}
public int getId() { return id; }
public void setId(int id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getDepartment() { return department; }
public void setDepartment(String department) { this.department = department; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
logic/StaffLogic.java
package logic;
import java.util.ArrayList;
import java.util.List;
import model.Staff;
public class StaffLogic {
// アプリケーション内でデータを共有するためstatic
private static final List<Staff> STAFF_LIST = new ArrayList<>();
private static int nextId = 1;
static {
add("田中太郎", "開発部", "tanaka@example.com");
add("鈴木花子", "営業部", "suzuki@example.com");
}
public List<Staff> findAll() {
return new ArrayList<>(STAFF_LIST);
}
public static void add(String name, String department, String email) {
Staff staff = new Staff(nextId++, name, department, email);
STAFF_LIST.add(staff);
}
public void addStaff(String name, String department, String email) {
add(name, department, email);
}
}
servlet/StaffListServlet.java
package servlet;
import java.io.IOException;
import java.util.List;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import logic.StaffLogic;
import model.Staff;
@WebServlet("/staff/list")
public class StaffListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
StaffLogic logic = new StaffLogic();
List<Staff> staffList = logic.findAll();
request.setAttribute("staffList", staffList);
request.getRequestDispatcher("/WEB-INF/view/staff-list.jsp")
.forward(request, response);
}
}
servlet/StaffAddServlet.java
package servlet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import logic.StaffLogic;
@WebServlet("/staff/add")
public class StaffAddServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/view/staff-add.jsp")
.forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String name = request.getParameter("name");
String department = request.getParameter("department");
String email = request.getParameter("email");
// バリデーション
List<String> errors = new ArrayList<>();
if (name == null || name.trim().isEmpty()) {
errors.add("名前を入力してください。");
}
if (department == null || department.trim().isEmpty()) {
errors.add("部署を入力してください。");
}
if (email == null || !email.contains("@")) {
errors.add("正しいメールアドレスを入力してください。");
}
if (!errors.isEmpty()) {
request.setAttribute("errors", errors);
request.setAttribute("name", name);
request.setAttribute("department", department);
request.setAttribute("email", email);
request.getRequestDispatcher("/WEB-INF/view/staff-add.jsp")
.forward(request, response);
return;
}
// 登録処理
StaffLogic logic = new StaffLogic();
logic.addStaff(name, department, email);
// 一覧画面にリダイレクト(PRGパターン)
response.sendRedirect(request.getContextPath() + "/staff/list");
}
}
WEB-INF/view/staff-list.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List, model.Staff" %>
<!DOCTYPE html>
<html>
<head>
<title>社員一覧</title>
<style>
table { border-collapse: collapse; }
th, td { border: 1px solid #ddd; padding: 8px; }
th { background-color: #336699; color: white; }
a { color: #336699; }
</style>
</head>
<body>
<h1>社員一覧</h1>
<p><a href="<%= request.getContextPath() %>/staff/add">新規登録</a></p>
<%
@SuppressWarnings("unchecked")
List<Staff> staffList = (List<Staff>) request.getAttribute("staffList");
%>
<table>
<tr><th>ID</th><th>名前</th><th>部署</th><th>メール</th></tr>
<% for (Staff s : staffList) { %>
<tr>
<td><%= s.getId() %></td>
<td><%= s.getName() %></td>
<td><%= s.getDepartment() %></td>
<td><%= s.getEmail() %></td>
</tr>
<% } %>
</table>
<p>全 <%= staffList.size() %> 件</p>
</body>
</html>
WEB-INF/view/staff-add.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<%@ page import="java.util.List" %>
<!DOCTYPE html>
<html>
<head><title>社員登録</title></head>
<body>
<h1>社員登録</h1>
<%
@SuppressWarnings("unchecked")
List<String> errors = (List<String>) request.getAttribute("errors");
if (errors != null && !errors.isEmpty()) {
%>
<div style="color: red; border: 1px solid red; padding: 10px;">
<ul>
<% for (String error : errors) { %>
<li><%= error %></li>
<% } %>
</ul>
</div>
<% } %>
<%
String name = request.getAttribute("name") != null ? (String) request.getAttribute("name") : "";
String department = request.getAttribute("department") != null ? (String) request.getAttribute("department") : "";
String email = request.getAttribute("email") != null ? (String) request.getAttribute("email") : "";
%>
<form action="<%= request.getContextPath() %>/staff/add" method="post">
<table>
<tr>
<td>名前:</td>
<td><input type="text" name="name" value="<%= name %>" /></td>
</tr>
<tr>
<td>部署:</td>
<td><input type="text" name="department" value="<%= department %>" /></td>
</tr>
<tr>
<td>メール:</td>
<td><input type="text" name="email" value="<%= email %>" /></td>
</tr>
<tr>
<td></td>
<td><button type="submit">登録</button></td>
</tr>
</table>
</form>
<p><a href="<%= request.getContextPath() %>/staff/list">一覧に戻る</a></p>
</body>
</html>
まとめ
| 学んだこと | キーワード |
|---|---|
| ロジックと表示の混在の問題 | 可読性・保守性・再利用性の低下 |
| MVCパターン | Model(データ/ロジック)、View(画面)、Controller(制御) |
| JavaBeanの規約 | private フィールド、引数なしコンストラクタ、getter/setter |
| データの受け渡し |
request.setAttribute() / request.getAttribute()
|
| フォワード |
RequestDispatcher.forward() で View に転送 |
| プロジェクト構成 | model / logic / servlet パッケージに分離 |
次回は JDBC連携(データベース操作) を学びます!
シリーズ一覧:Servlet/JSP入門
- 環境構築とはじめてのServlet
- HTTPリクエストとレスポンス
- JSPの基礎
- フォーム処理(GET/POST)
- セッション管理とCookie
- 👉 MVCパターン(Servlet + JSP)(本記事)
- JDBC連携(データベース操作)
- EL式とJSTL
- フィルターとリスナー
- 総合演習:掲示板アプリを作ろう
著者: @kotaro_ai_lab
AI駆動開発やテック情報を毎日発信しています。フォローお気軽にどうぞ!