はじめに
Servlet/JSP入門の第7回は JDBC連携(データベース操作) です。
Webアプリケーションではほぼ必ずデータベースを使います。ユーザー情報、商品データ、注文履歴......これらはすべてDBに保存されます。JavaからDBに接続するための標準APIが JDBC(Java Database Connectivity) です。
第7回で学ぶこと
- JDBCの概要とドライバの導入
- Connection, Statement, PreparedStatement, ResultSet の使い方
- CRUD操作(Create, Read, Update, Delete)
- SQLインジェクション対策
- DAOパターン(Data Access Object)
- コネクションプーリングの基礎
- 完成例:ユーザー管理Webアプリ
1. JDBCの概要
JDBCとは?
JDBC は、JavaプログラムからリレーショナルDBに接続し、SQLを実行するための標準APIです。
┌──────────┐ JDBC API ┌──────────────┐
│ │ ──────────────────→ │ │
│ Java │ │ データベース │
│ アプリ │ ←────────────────── │ (MySQL等) │
│ │ 結果セット │ │
└──────────┘ └──────────────┘
↕
JDBCドライバ(各DB用)
JDBCの処理の流れ
1. JDBCドライバをロード
2. データベースに接続(Connection取得)
3. SQL文を作成(Statement / PreparedStatement)
4. SQLを実行
5. 結果を処理(ResultSet)
6. リソースを解放(close)
使用するデータベース
本記事では MySQL を使用します(H2 Database でも基本は同じです)。
| DB | 特徴 | JDBCドライバ |
|---|---|---|
| MySQL | 最も広く使われるOSS RDBMS | mysql-connector-j |
| H2 Database | Java製の軽量DB(組み込み可能) | h2 |
| PostgreSQL | 高機能なOSS RDBMS | postgresql |
2. 環境準備
MySQLのセットアップ
MySQLをインストールし、テスト用のデータベースとテーブルを作成します。
-- データベース作成
CREATE DATABASE web_study DEFAULT CHARACTER SET utf8mb4;
-- データベースを使用
USE web_study;
-- ユーザーテーブル作成
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- テストデータ挿入
INSERT INTO users (name, email, age) VALUES
('田中太郎', 'tanaka@example.com', 25),
('鈴木花子', 'suzuki@example.com', 30),
('佐藤次郎', 'sato@example.com', 28);
JDBCドライバの導入
方法1:手動でJARを追加
-
Maven Repository から
mysql-connector-j-x.x.x.jarをダウンロード - プロジェクトの
WEB-INF/lib/にコピー
方法2:Mavenを使う場合
pom.xml に依存を追加:
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.3.0</version>
</dependency>
3. JDBCの基本操作
3.1 データベースへの接続
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DBConnectionTest {
public static void main(String[] args) {
// JDBC接続情報
String url = "jdbc:mysql://localhost:3306/web_study?useSSL=false&serverTimezone=Asia/Tokyo";
String user = "root";
String password = "your_password";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
System.out.println("データベースに接続しました!");
System.out.println("DB製品名: " + conn.getMetaData().getDatabaseProductName());
System.out.println("DBバージョン: " + conn.getMetaData().getDatabaseProductVersion());
} catch (SQLException e) {
System.out.println("接続に失敗しました: " + e.getMessage());
}
}
}
JDBC URLの構成
jdbc:mysql://localhost:3306/web_study?useSSL=false&serverTimezone=Asia/Tokyo
│ │ │ │ │ │
│ │ │ │ │ └─ オプション(パラメータ)
│ │ │ │ └─ データベース名
│ │ │ └─ ポート番号
│ │ └─ ホスト名
│ └─ DBの種類
└─ JDBCプロトコル
3.2 データの検索(SELECT)
import java.sql.*;
public class SelectExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/web_study?useSSL=false&serverTimezone=Asia/Tokyo";
String user = "root";
String password = "your_password";
String sql = "SELECT id, name, email, age FROM users";
try (Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql)) {
System.out.println("=== ユーザー一覧 ===");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
int age = rs.getInt("age");
System.out.println(id + ": " + name + " (" + email + ") " + age + "歳");
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
出力例:
=== ユーザー一覧 ===
1: 田中太郎 (tanaka@example.com) 25歳
2: 鈴木花子 (suzuki@example.com) 30歳
3: 佐藤次郎 (sato@example.com) 28歳
3.3 データの挿入(INSERT)
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, "高橋美咲");
pstmt.setString(2, "takahashi@example.com");
pstmt.setInt(3, 22);
int rows = pstmt.executeUpdate();
System.out.println(rows + "件のデータを挿入しました。");
} catch (SQLException e) {
e.printStackTrace();
}
3.4 データの更新(UPDATE)
String sql = "UPDATE users SET age = ? WHERE name = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 26);
pstmt.setString(2, "田中太郎");
int rows = pstmt.executeUpdate();
System.out.println(rows + "件のデータを更新しました。");
} catch (SQLException e) {
e.printStackTrace();
}
3.5 データの削除(DELETE)
String sql = "DELETE FROM users WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, 4);
int rows = pstmt.executeUpdate();
System.out.println(rows + "件のデータを削除しました。");
} catch (SQLException e) {
e.printStackTrace();
}
JDBC主要クラスまとめ
| クラス/インターフェース | 役割 |
|---|---|
DriverManager |
DBへの接続を管理 |
Connection |
DBとの接続を表す |
Statement |
SQL文を実行する(固定SQL向き) |
PreparedStatement |
パラメータ付きSQL文を実行する(推奨) |
ResultSet |
SELECTの結果セットを保持 |
SQL実行メソッドの使い分け
| メソッド | 用途 | 戻り値 |
|---|---|---|
executeQuery() |
SELECT文 | ResultSet |
executeUpdate() |
INSERT/UPDATE/DELETE文 |
int(影響行数) |
4. SQLインジェクション対策
危険な例:文字列連結でSQLを組み立てる
// ❌ 絶対にやってはいけない!
String name = request.getParameter("name");
String sql = "SELECT * FROM users WHERE name = '" + name + "'";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(sql);
もし name に ' OR '1'='1 と入力されると......
SELECT * FROM users WHERE name = '' OR '1'='1'
全レコードが取得されてしまいます!さらに '; DROP TABLE users; -- のような入力でテーブルが削除される危険もあります。
安全な例:PreparedStatement を使う
// ✅ PreparedStatementを使えば安全
String name = request.getParameter("name");
String sql = "SELECT * FROM users WHERE name = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
ResultSet rs = pstmt.executeQuery();
?(プレースホルダ)に値をバインドすることで、入力値がSQLの構文として解釈されることを防ぎます。
| 方法 | 安全性 | 用途 |
|---|---|---|
Statement + 文字列連結 |
危険 | パラメータなしの固定SQL |
PreparedStatement + ?
|
安全 | パラメータありのSQL(常にこちらを使う) |
鉄則:ユーザー入力をSQLに含める場合は、必ず PreparedStatement を使う!
5. DAOパターン(Data Access Object)
DAOパターンとは?
DAO は、データベースへのアクセス処理を1つのクラスに集約するデザインパターンです。
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Servlet │ ──→ │ DAO │ ──→ │ Database │
│(Controller)│ │ │ │ │
└──────────┘ └──────────┘ └──────────┘
↑
DB操作はすべてDAOに集約
メリット:
- DB操作のコードが一箇所にまとまる
- ServletにSQL文が散らばらない
- DBを変更しても影響範囲がDAO内に限定される
5.1 Userモデルクラス(JavaBean)
package model;
import java.sql.Timestamp;
public class User {
private int id;
private String name;
private String email;
private int age;
private Timestamp createdAt;
// デフォルトコンストラクタ
public User() {}
// パラメータ付きコンストラクタ
public User(String name, String email, int age) {
this.name = name;
this.email = email;
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 getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
public Timestamp getCreatedAt() { return createdAt; }
public void setCreatedAt(Timestamp createdAt) { this.createdAt = createdAt; }
}
5.2 UserDAO クラス
package dao;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import model.User;
public class UserDAO {
private static final String URL = "jdbc:mysql://localhost:3306/web_study?useSSL=false&serverTimezone=Asia/Tokyo";
private static final String DB_USER = "root";
private static final String DB_PASS = "your_password";
// DB接続を取得
private Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, DB_USER, DB_PASS);
}
// 全件取得
public List<User> findAll() {
List<User> users = new ArrayList<>();
String sql = "SELECT id, name, email, age, created_at FROM users ORDER BY id";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
user.setAge(rs.getInt("age"));
user.setCreatedAt(rs.getTimestamp("created_at"));
users.add(user);
}
} catch (SQLException e) {
e.printStackTrace();
}
return users;
}
// IDで1件取得
public User findById(int id) {
String sql = "SELECT id, name, email, age, created_at FROM users WHERE id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
User user = new User();
user.setId(rs.getInt("id"));
user.setName(rs.getString("name"));
user.setEmail(rs.getString("email"));
user.setAge(rs.getInt("age"));
user.setCreatedAt(rs.getTimestamp("created_at"));
return user;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
// 新規登録
public boolean insert(User user) {
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getName());
pstmt.setString(2, user.getEmail());
pstmt.setInt(3, user.getAge());
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
// 更新
public boolean update(User user) {
String sql = "UPDATE users SET name = ?, email = ?, age = ? WHERE id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getName());
pstmt.setString(2, user.getEmail());
pstmt.setInt(3, user.getAge());
pstmt.setInt(4, user.getId());
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
// 削除
public boolean delete(int id) {
String sql = "DELETE FROM users WHERE id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
}
6. コネクションプーリング
なぜコネクションプーリングが必要?
毎回 DriverManager.getConnection() で接続を作ると、接続のたびにTCP通信・認証が発生し非常に遅くなります。
毎回新規接続の場合:
リクエスト1 → 接続作成(遅い) → SQL実行 → 接続破棄
リクエスト2 → 接続作成(遅い) → SQL実行 → 接続破棄
コネクションプーリングの場合:
起動時 → 接続を複数作成してプールに保持
リクエスト1 → プールから借りる(速い) → SQL実行 → プールに返す
リクエスト2 → プールから借りる(速い) → SQL実行 → プールに返す
Tomcatのコネクションプール設定
src/main/webapp/META-INF/context.xml を作成:
<?xml version="1.0" encoding="UTF-8"?>
<Context>
<Resource
name="jdbc/web_study"
auth="Container"
type="javax.sql.DataSource"
maxTotal="20"
maxIdle="5"
maxWaitMillis="10000"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://localhost:3306/web_study?useSSL=false&serverTimezone=Asia/Tokyo"
username="root"
password="your_password" />
</Context>
src/main/webapp/WEB-INF/web.xml にリソース参照を追加:
<resource-ref>
<res-ref-name>jdbc/web_study</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
DAOでコネクションプールを使う
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
public class UserDAO {
private DataSource dataSource;
public UserDAO() {
try {
Context initContext = new InitialContext();
Context envContext = (Context) initContext.lookup("java:comp/env");
dataSource = (DataSource) envContext.lookup("jdbc/web_study");
} catch (Exception e) {
throw new RuntimeException("DataSourceの取得に失敗しました", e);
}
}
private Connection getConnection() throws SQLException {
return dataSource.getConnection(); // プールから接続を取得
}
// 以降のメソッドは同じ
}
| 方法 | メリット | デメリット |
|---|---|---|
DriverManager |
シンプル | 毎回接続で遅い |
| コネクションプール | 高速、リソース管理が効率的 | 設定が必要 |
学習段階では
DriverManagerで十分です。 実務ではコネクションプールが必須になります。
7. 完成例:ユーザー管理Webアプリ
DAOパターンを使って、ブラウザからユーザーの一覧表示・追加・削除ができるアプリを作ります。
プロジェクト構成
WebStudy/
├── src/main/java/
│ ├── model/
│ │ └── User.java ← データモデル
│ ├── dao/
│ │ └── UserDAO.java ← DB操作
│ └── servlet/
│ ├── UserListServlet.java ← 一覧表示
│ ├── UserAddServlet.java ← ユーザー追加
│ └── UserDeleteServlet.java ← ユーザー削除
├── src/main/webapp/
│ └── WEB-INF/
│ └── jsp/
│ ├── userList.jsp ← 一覧画面
│ └── userForm.jsp ← 登録フォーム
UserListServlet.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 dao.UserDAO;
import model.User;
@WebServlet("/users")
public class UserListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
UserDAO dao = new UserDAO();
List<User> users = dao.findAll();
request.setAttribute("users", users);
request.getRequestDispatcher("/WEB-INF/jsp/userList.jsp").forward(request, response);
}
}
UserAddServlet.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 dao.UserDAO;
import model.User;
@WebServlet("/users/add")
public class UserAddServlet extends HttpServlet {
// フォーム表示
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/userForm.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 email = request.getParameter("email");
int age = Integer.parseInt(request.getParameter("age"));
User user = new User(name, email, age);
UserDAO dao = new UserDAO();
dao.insert(user);
// 一覧画面にリダイレクト
response.sendRedirect(request.getContextPath() + "/users");
}
}
UserDeleteServlet.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 dao.UserDAO;
@WebServlet("/users/delete")
public class UserDeleteServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
int id = Integer.parseInt(request.getParameter("id"));
UserDAO dao = new UserDAO();
dao.delete(id);
response.sendRedirect(request.getContextPath() + "/users");
}
}
userList.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ page import="java.util.List" %>
<%@ page import="model.User" %>
<!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: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
.btn { padding: 5px 10px; color: white; border: none; cursor: pointer; border-radius: 3px; }
.btn-danger { background-color: #f44336; }
.btn-primary { background-color: #2196F3; text-decoration: none; padding: 8px 16px; }
</style>
</head>
<body>
<h1>ユーザー管理</h1>
<p><a href="<%= request.getContextPath() %>/users/add" class="btn btn-primary">新規登録</a></p>
<%
List<User> users = (List<User>) request.getAttribute("users");
%>
<table>
<tr>
<th>ID</th>
<th>名前</th>
<th>メールアドレス</th>
<th>年齢</th>
<th>登録日時</th>
<th>操作</th>
</tr>
<% for (User user : users) { %>
<tr>
<td><%= user.getId() %></td>
<td><%= user.getName() %></td>
<td><%= user.getEmail() %></td>
<td><%= user.getAge() %></td>
<td><%= user.getCreatedAt() %></td>
<td>
<form action="<%= request.getContextPath() %>/users/delete" method="post"
style="display:inline;"
onsubmit="return confirm('本当に削除しますか?');">
<input type="hidden" name="id" value="<%= user.getId() %>">
<button type="submit" class="btn btn-danger">削除</button>
</form>
</td>
</tr>
<% } %>
</table>
</body>
</html>
userForm.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<title>ユーザー登録</title>
<style>
body { font-family: sans-serif; margin: 20px; }
label { display: block; margin-top: 10px; font-weight: bold; }
input[type="text"], input[type="email"], input[type="number"] {
padding: 8px; width: 300px; margin-top: 5px;
}
.btn { padding: 10px 20px; color: white; border: none; cursor: pointer; margin-top: 15px; border-radius: 3px; }
.btn-primary { background-color: #4CAF50; }
</style>
</head>
<body>
<h1>ユーザー登録</h1>
<form action="<%= request.getContextPath() %>/users/add" method="post">
<label>名前</label>
<input type="text" name="name" required>
<label>メールアドレス</label>
<input type="email" name="email" required>
<label>年齢</label>
<input type="number" name="age" min="0" max="150" required>
<br>
<button type="submit" class="btn btn-primary">登録</button>
</form>
<p><a href="<%= request.getContextPath() %>/users">← 一覧に戻る</a></p>
</body>
</html>
練習問題
問題1:商品テーブルのCRUD ⭐
以下のSQLでテーブルを作成し、JDBC で全件取得してコンソールに表示するプログラムを作成してください。
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
price INT NOT NULL
);
INSERT INTO products (name, price) VALUES
('ノートPC', 98000),
('マウス', 2500),
('キーボード', 8000);
模範解答
package exercise;
import java.sql.*;
public class ProductSelect {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/web_study?useSSL=false&serverTimezone=Asia/Tokyo";
String user = "root";
String password = "your_password";
String sql = "SELECT id, name, price FROM products ORDER BY id";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
System.out.println("=== 商品一覧 ===");
System.out.printf("%-5s %-15s %s%n", "ID", "商品名", "価格");
System.out.println("-".repeat(35));
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int price = rs.getInt("price");
System.out.printf("%-5d %-15s %,d円%n", id, name, price);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
ポイント: try-with-resources を使うことで、Connection、PreparedStatement、ResultSet を自動的に閉じることができます。リソースの閉じ忘れはバグの原因になるため、必ず使いましょう。
問題2:検索機能付きServlet ⭐⭐
名前でユーザーをあいまい検索できるServletを作成してください。
- URLパターン:
/users/search - GETパラメータ
keywordで検索 -
LIKEを使ったあいまい検索 - 結果をHTMLテーブルで表示
模範解答
package servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.*;
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;
@WebServlet("/users/search")
public class UserSearchServlet extends HttpServlet {
private static final String URL = "jdbc:mysql://localhost:3306/web_study?useSSL=false&serverTimezone=Asia/Tokyo";
private static final String DB_USER = "root";
private static final String DB_PASS = "your_password";
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
response.setContentType("text/html; charset=UTF-8");
PrintWriter out = response.getWriter();
String keyword = request.getParameter("keyword");
out.println("<!DOCTYPE html>");
out.println("<html><head><title>ユーザー検索</title></head><body>");
out.println("<h1>ユーザー検索</h1>");
out.println("<form action='" + request.getRequestURI() + "' method='get'>");
out.println(" 名前: <input type='text' name='keyword' value='" + (keyword != null ? keyword : "") + "'>");
out.println(" <button type='submit'>検索</button>");
out.println("</form>");
if (keyword != null && !keyword.isEmpty()) {
String sql = "SELECT id, name, email, age FROM users WHERE name LIKE ?";
try (Connection conn = DriverManager.getConnection(URL, DB_USER, DB_PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, "%" + keyword + "%");
try (ResultSet rs = pstmt.executeQuery()) {
out.println("<h2>検索結果(キーワード: " + keyword + ")</h2>");
out.println("<table border='1'>");
out.println("<tr><th>ID</th><th>名前</th><th>メール</th><th>年齢</th></tr>");
int count = 0;
while (rs.next()) {
out.println("<tr>");
out.println("<td>" + rs.getInt("id") + "</td>");
out.println("<td>" + rs.getString("name") + "</td>");
out.println("<td>" + rs.getString("email") + "</td>");
out.println("<td>" + rs.getInt("age") + "</td>");
out.println("</tr>");
count++;
}
out.println("</table>");
out.println("<p>" + count + "件見つかりました。</p>");
}
} catch (SQLException e) {
out.println("<p style='color:red;'>エラー: " + e.getMessage() + "</p>");
}
}
out.println("</body></html>");
}
}
ポイント: LIKE 検索のワイルドカード % は Java側で "%" + keyword + "%" のように付与し、PreparedStatement の setString でバインドします。こうすればSQLインジェクションも防げます。
問題3:DAOパターンで商品管理 ⭐⭐⭐
以下の要件で、DAOパターンを使った商品管理アプリを作成してください。
-
Productモデルクラス(id, name, price) -
ProductDAOクラス(findAll, findById, insert, update, delete) -
ProductListServlet(/productsで一覧表示) -
ProductAddServlet(/products/addでGET→フォーム表示、POST→登録処理) - JSPで画面を作成
模範解答
Product.java
package model;
public class Product {
private int id;
private String name;
private int price;
public Product() {}
public Product(String name, int price) {
this.name = name;
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 int getPrice() { return price; }
public void setPrice(int price) { this.price = price; }
}
ProductDAO.java
package dao;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import model.Product;
public class ProductDAO {
private static final String URL = "jdbc:mysql://localhost:3306/web_study?useSSL=false&serverTimezone=Asia/Tokyo";
private static final String DB_USER = "root";
private static final String DB_PASS = "your_password";
private Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, DB_USER, DB_PASS);
}
public List<Product> findAll() {
List<Product> products = new ArrayList<>();
String sql = "SELECT id, name, price FROM products ORDER BY id";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
Product p = new Product();
p.setId(rs.getInt("id"));
p.setName(rs.getString("name"));
p.setPrice(rs.getInt("price"));
products.add(p);
}
} catch (SQLException e) {
e.printStackTrace();
}
return products;
}
public Product findById(int id) {
String sql = "SELECT id, name, price FROM products WHERE id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
try (ResultSet rs = pstmt.executeQuery()) {
if (rs.next()) {
Product p = new Product();
p.setId(rs.getInt("id"));
p.setName(rs.getString("name"));
p.setPrice(rs.getInt("price"));
return p;
}
}
} catch (SQLException e) {
e.printStackTrace();
}
return null;
}
public boolean insert(Product product) {
String sql = "INSERT INTO products (name, price) VALUES (?, ?)";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, product.getName());
pstmt.setInt(2, product.getPrice());
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public boolean update(Product product) {
String sql = "UPDATE products SET name = ?, price = ? WHERE id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, product.getName());
pstmt.setInt(2, product.getPrice());
pstmt.setInt(3, product.getId());
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
public boolean delete(int id) {
String sql = "DELETE FROM products WHERE id = ?";
try (Connection conn = getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setInt(1, id);
return pstmt.executeUpdate() > 0;
} catch (SQLException e) {
e.printStackTrace();
}
return false;
}
}
ProductListServlet.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 dao.ProductDAO;
import model.Product;
@WebServlet("/products")
public class ProductListServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
ProductDAO dao = new ProductDAO();
List<Product> products = dao.findAll();
request.setAttribute("products", products);
request.getRequestDispatcher("/WEB-INF/jsp/productList.jsp").forward(request, response);
}
}
ProductAddServlet.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 dao.ProductDAO;
import model.Product;
@WebServlet("/products/add")
public class ProductAddServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.getRequestDispatcher("/WEB-INF/jsp/productForm.jsp").forward(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String name = request.getParameter("name");
int price = Integer.parseInt(request.getParameter("price"));
Product product = new Product(name, price);
new ProductDAO().insert(product);
response.sendRedirect(request.getContextPath() + "/products");
}
}
ポイント: DAOパターンにより、Servlet(Controller)にはDB操作のコードが一切含まれません。責務が明確に分離され、テストやメンテナンスが容易になります。
まとめ
| 学んだこと | キーワード |
|---|---|
| JDBCの概要 | JDBC URL、DriverManager、JDBCドライバ |
| 接続と操作 | Connection、Statement、PreparedStatement、ResultSet |
| CRUD操作 | SELECT(executeQuery)、INSERT/UPDATE/DELETE(executeUpdate) |
| セキュリティ | SQLインジェクション、PreparedStatement の ?
|
| DAOパターン | DB操作の集約、Model + DAO の分離 |
| コネクションプーリング | DataSource、Tomcat の Resource 設定 |
次回は EL式とJSTL を学び、JSPのスクリプトレット地獄から解放されましょう!
シリーズ一覧:Servlet/JSP入門
- 環境構築とはじめてのServlet
- HTTPリクエストとレスポンス
- JSPの基礎
- フォーム処理(GET/POST)
- セッション管理とCookie
- MVCパターン(Servlet + JSP)
- 👉 JDBC連携(データベース操作)(本記事)
- EL式とJSTL
- フィルターとリスナー
- 総合演習:掲示板アプリを作ろう
著者: @kotaro_ai_lab
AI駆動開発やテック情報を毎日発信しています。フォローお気軽にどうぞ!