Java ServletとJDBCでデータベース保存!Webフォームからのデータ送信を完全解説
新人
「Webサイトのお問い合わせフォームから入力した名前やメールアドレスを、データベースに保存する仕組みを作りたいのですが、どうすればいいでしょうか?」
先輩
「Java Web開発の王道パターンですね。まずブラウザからデータを受け取るための『Servlet(サーブレット)』と、データベースを操作するための『JDBC』という技術を組み合わせる必要があります。」
新人
「ServletとJDBC……難しそうですね。何から手をつければいいですか?」
先輩
「大丈夫ですよ。まずは全体の流れを把握して、一つずつ部品を作っていきましょう。それでは、基本的な仕組みから見ていきましょう!」
1. フォームからDB保存までの全体像:ServletとJDBCの役割
Webアプリケーションにおいて、ユーザーが画面から入力した情報をデータベース(DB)に保存するまでの流れは、よく「バケツリレー」に例えられます。 Javaの世界では、このリレーの走者として主にServlet(サーブレット)とJDBC(ジェーディービーシー)という2つの重要な技術が登場します。
処理の全体フロー
ユーザーがブラウザ上でボタンをクリックしてから、データが保存されるまでのステップは以下の通りです。
- 入力フォーム(HTML/JSP): ユーザーが文字を入力し、「送信」ボタンを押します。
- リクエストの送信: インターネットを通じてデータがサーバーに送られます。
- Servletの受け取り: サーバー側で待ち構えているJavaプログラム(Servlet)が、送られてきたデータをキャッチします。
- JDBCによるDB操作: Servletが受け取ったデータを、JDBCという仲介役を使ってデータベースへ書き込みます。
各技術の具体的な役割
Servletは、ブラウザ(クライアント)からの要求(リクエスト)を受け付け、適切な処理を行ってから結果を返す役割を持ちます。 「画面からのデータを受け取る」「入力チェックをする」といった、Webアプリケーションの司令塔のような存在です。
JDBC(Java Database Connectivity)は、Javaからデータベースへ接続するための標準的な仕組みです。 データベースに対して「このデータを保存して!」「このデータを探して!」といった命令(SQL文)を発行し、実際のデータの読み書きを担当します。
この2つが連携することで、私たちは普段使っているSNSの投稿機能や、ネットショッピングの注文システムを利用することができるのです。 プログラミング未経験の方は、まず「Servletがデータを受け取り、JDBCがそれをDBに運ぶ」というイメージをしっかり持ちましょう。
2. 入力フォームの作成とデータの送信方法(POST通信)
データを送るためには、まずユーザーが入力するための「画面」が必要です。HTMLの<form>タグを使用して作成します。
ここで非常に重要なのが、データの送り方である「メソッド(通信方式)」の選択です。
GET通信とPOST通信の違い
Webには大きく分けてGET(ゲット)とPOST(ポスト)という2種類の送り方があります。
- GET: URLの後ろにデータをくっつけて送る方式。検索結果の表示など、公開されても良い軽いデータに向いています。
- POST: 封筒の中にデータを入れて送るような方式。URLには表示されないため、パスワードや個人情報、大量のデータを送る際に向いています。
データベースに情報を登録する場合は、安全性の観点からPOST通信を使うのが鉄則です。
入力フォームのサンプルコード(HTML/JSP)
ユーザー名と年齢を入力して送信するシンプルなフォームを作成してみましょう。
action属性には送り先のServletのURLを、method属性にはpostを指定します。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>ユーザー登録フォーム</title>
</head>
<body>
<h1>ユーザー情報の登録</h1>
<!-- actionで送り先のサーブレットを指定、methodでPOSTを指定 -->
<form action="RegisterServlet" method="post">
<label for="userName">お名前:</label>
<input type="text" id="userName" name="userName" required>
<br><br>
<label for="userAge">年齢:</label>
<input type="number" id="userAge" name="userAge" required>
<br><br>
<button type="submit">送信する</button>
</form>
</body>
</html>
実装のポイント
フォームを作成する際、もっとも重要なのは<input>タグのname属性です。
このname="userName"という名前が、Javaプログラム側でデータを取り出す際の「キー(鍵)」になります。
ここを間違えると、Servlet側でデータを受け取ることができなくなるので、必ずServlet側のコードと一致させるようにしましょう。
3. Servletでリクエストパラメータを取得する際の実装ポイント
フォームから送られたデータは、サーバー側のServletで「リクエストパラメータ」として取得します。
JavaのServletクラスには、POST通信を受け取るためのdoPostという専用のメソッドが用意されています。
文字化けを防ぐための設定
日本語を扱う場合、まず最初に行わなければならないのが文字エンコーディングの設定です。
これを行わないと、日本語が「???」のように文字化けしてしまいます。
必ず、データを取り出す前にsetCharacterEncoding("UTF-8")を呼び出す必要があります。
Servletのサンプルコード(Java)
以下のコードは、フォームから送られてきた「お名前」と「年齢」を受け取り、コンソールに表示する例です。
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* ユーザー登録処理を行うサーブレット
*/
@WebServlet("/RegisterServlet")
public class RegisterServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. 文字化け対策(一番最初に記述!)
request.setCharacterEncoding("UTF-8");
// 2. フォームから送られたデータを取得(name属性の値を指定)
String name = request.getParameter("userName");
String ageStr = request.getParameter("userAge");
// 3. 型の変換(数字として扱いたい場合は変換が必要)
int age = 0;
if (ageStr != null && !ageStr.isEmpty()) {
age = Integer.parseInt(ageStr);
}
// 4. 取得した結果を確認(デバッグ用)
System.out.println("受信した名前: " + name);
System.out.println("受信した年齢: " + age);
// ※この後、JDBCを使用してデータベース保存処理へ移ります
}
}
実装の重要ルール
Servletでリクエストパラメータを扱う際には、以下の3点に注意しましょう。
- getParameterメソッド: 引数にはHTMLの
name属性で指定した文字列を正確に入れます。 - 戻り値は常にString型:
getParameterで取得した値は、たとえ数字であっても最初は「文字列(String)」として届きます。計算に使いたい場合は、Integer.parseInt()などで数値型に変換する必要があります。 - nullチェックと空文字チェック: ユーザーが何も入力せずに送信した場合や、想定外の挙動に備えて、データが空でないかを確認する処理を入れるのが「プロの書き方」です。
ここまでで、「画面で入力した値をJavaプログラムが認識する」という第一段階が完了しました。 この後、いよいよJDBCを用いて、この受け取ったデータをデータベースという「情報の保管庫」へ送り届ける処理に進むことになります。 JDBCの設定には、データベース接続用の「URL」「ユーザー名」「パスワード」といった設定情報が必要になりますので、準備しておきましょう。
4. JDBCを利用したデータベース接続(DriverManagerとDataSource)
Servletで受け取ったデータをデータベースに届けるためには、Javaプログラムとデータベースの間に「道」を通す必要があります。この接続作業を担うのがJDBC(Java Database Connectivity)です。 Javaでデータベース接続を確立する方法には、主に「DriverManager」を利用する方法と「DataSource」を利用する方法の2種類があります。それぞれの特徴と、現場での使い分けについて詳しく解説します。
DriverManagerによる基本接続
DriverManagerは、JDBCの最も基本的で伝統的な接続方法です。学習段階や小規模なツール作成でよく利用されます。 接続を確立するためには、データベースの場所を示す「接続URL」、アクセスするための「ユーザー名」、そして「パスワード」の3つが不可欠です。
具体的な接続の流れは、まず使用するデータベースに対応した「JDBCドライバ」を読み込み、次にDriverManager.getConnection()メソッドを呼び出すことで、データベースとのセッション(Connectionオブジェクト)を取得します。
このConnectionオブジェクトこそが、データベース操作におけるすべての起点となります。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DatabaseManager {
// 接続に必要な情報
private static final String URL = "jdbc:mysql://localhost:3306/my_database";
private static final String USER = "root";
private static final String PASS = "password123";
public static Connection getConnection() throws SQLException {
try {
// JDBCドライバのロード(現在のJavaでは省略可能な場合が多いですが、明示的に書くのが確実です)
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
// データベースへの接続を確立して返す
return DriverManager.getConnection(URL, USER, PASS);
}
}
DataSourceとコネクションプーリング
実際の現場や大規模なWebアプリケーションでは、DriverManagerを直接使うことは稀です。代わりにDataSource(データソース)と、それに付随する「コネクションプーリング」という技術が使われます。
データベースへの接続は非常に「コストが高い(時間がかかる)」処理です。リクエストが来るたびに接続と切断を繰り返すと、サーバーの負荷が跳ね上がり、動作が重くなってしまいます。 そこで、あらかじめいくつかの接続を「プール(貯水池)」に作っておき、必要に応じて貸し出し、使い終わったら返却してもらう仕組みが考案されました。これがコネクションプーリングです。
DataSourceを利用するメリットは、接続情報の管理をプログラムから切り離せる点にあります。Tomcatなどのアプリケーションサーバー側で設定を保持し、Java側からは「JNDI」という仕組みを使って名前で呼び出すのが一般的です。 これにより、開発環境と本番環境でデータベースが異なる場合でも、ソースコードを書き換えることなく対応が可能になります。
5. PreparedStatementによるSQL(INSERT文)の実行手順
データベースとの接続ができたら、次はいよいよデータの登録です。Webフォームから届いた情報をテーブルに保存するには、SQLのINSERT文を実行します。
JavaでSQLを実行する際、もっとも推奨されるのがPreparedStatement(プリペアード・ステートメント)を使用する方法です。
PreparedStatementの仕組み
通常のStatementと異なり、PreparedStatementは「SQLのテンプレート」をあらかじめデータベース側に送り、解析(コンパイル)させておく仕組みです。 SQL文の中の「値が入る部分」を「?」(プレースホルダ)として記述し、後からその部分に実際のデータを流し込みます。
この方式には、大きく分けて2つのメリットがあります。
- 処理の高速化: 同じSQLを何度も実行する場合、解析済みのテンプレートを使い回すため、処理効率が向上します。
- セキュリティの向上: 後述するSQLインジェクション攻撃を構造的に防ぐことができます。
INSERT文の実行サンプルコード
以下は、ユーザー名と年齢をデータベースのusersテーブルに保存する具体的なコード例です。
例外処理(try-with-resources構文)を使い、処理が終わったら自動的に接続を閉じるように実装するのが現代的なJavaの書き方です。
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class UserDAO {
// 実行するSQL文(値の部分は「?」にする)
private static final String INSERT_SQL = "INSERT INTO users (name, age) VALUES (?, ?)";
public void registerUser(String name, int age) {
// try-with-resourcesを使用してリソースのクローズを自動化
try (Connection conn = DatabaseManager.getConnection();
PreparedStatement pstmt = conn.prepareStatement(INSERT_SQL)) {
// プレースホルダに値をセット(1番目から順に指定)
pstmt.setString(1, name);
pstmt.setInt(2, age);
// SQLの実行(更新系SQLはexecuteUpdateを使用)
int rowsAffected = pstmt.executeUpdate();
if (rowsAffected > 0) {
System.out.println("登録に成功しました。");
}
} catch (SQLException e) {
System.err.println("データベース操作中にエラーが発生しました。");
e.printStackTrace();
}
}
}
executeUpdateメソッドの戻り値
executeUpdate()メソッドは、実行結果として「影響を受けた行数」を整数(int)で返します。
新規登録(INSERT)であれば、正常に完了すれば「1」が返ってきます。この戻り値を確認することで、プログラム側で処理が成功したかどうかを判定し、画面に「登録完了」などのメッセージを出す制御が可能になります。
6. SQLインジェクションを防ぐためのプレースホルダ活用法
Webアプリケーションを公開する上で、絶対に無視できないのがセキュリティ対策です。その中でも最も有名で、かつ被害が大きい攻撃の一つがSQLインジェクションです。 PreparedStatementとプレースホルダを正しく使うことは、この恐ろしい攻撃からアプリケーションを守るための最強の盾となります。
SQLインジェクションとは何か?
SQLインジェクションとは、ユーザーの入力値の中に「SQLの一部を改変するような悪意のある文字列」を混ぜ込み、データベースを不正に操作する攻撃手法です。 例えば、文字列連結でSQLを作っている場合、入力欄に「' OR '1'='1」といった値を入れられると、パスワード認証をバイパスされたり、テーブル内の全データが削除されたりする危険があります。
なぜプレースホルダが安全なのか
プレースホルダ(?)を使用すると、入力された値は「SQL命令の一部」としてではなく、単なる「データ(文字列や数値)」として厳格に扱われます。 データベース側は、あらかじめ「ここは名前が入る場所だ」と認識しているため、その中にどれだけ複雑なSQL構文が混じっていても、それを実行することはありません。
以下に、危険なコードと安全なコードの比較を示します。
String sql = "SELECT * FROM users WHERE id = '" + inputId + "'";
もしinputIdに' OR '1'='1と入力されたら、全てのユーザー情報が流出してしまいます。
String sql = "SELECT * FROM users WHERE id = ?";
pstmt.setString(1, inputId);
入力値がそのままデータとして処理されるため、命令が書き換えられる心配がありません。
セキュリティ向上のためのベストプラクティス
JDBCを扱う際は、以下のルールを徹底しましょう。
- 絶対に文字列連結でSQLを作らない: どんなに短いSQLであっても、プレースホルダを使う習慣をつけましょう。
- データ型を意識する:
setStringだけでなく、数値にはsetInt、日付にはsetDateといった適切なメソッドを使うことで、型チェックの恩恵も受けられます。 - 例外処理を適切に行う: エラーメッセージをそのままユーザーの画面に表示すると、データベースの構造(テーブル名など)を攻撃者に教えることになります。ログには詳細を出しつつ、画面には「システムエラーが発生しました」といった抽象的な案内を出すのが鉄則です。
Webフォームから受け取った大切なデータを守り、正しくデータベースへ届ける一連の流れを理解できたでしょうか。 Servletによる受付、JDBCによる道作り、そしてPreparedStatementによる安全な書き込み。これらはJavaエンジニアとして必須のスキルです。
7. 実装コードの全容:ServletからDB保存までのサンプルプログラム
これまでに学んだ要素をすべて統合し、実際に動作するアプリケーションの形にまとめましょう。 ここでは、ユーザーがフォームに入力した「名前」と「年齢」をデータベースの「users」テーブルに保存するまでの一連の流れを、完全なソースコードで示します。
システムの構成は、データを保持するJavaBeansであるUserEntity、データベース接続を管理するDatabaseUtil、データベース操作を行うUserDAO、そしてWebリクエストを処理するRegisterServletの4つの部品で構成します。
1. データ保持用クラス(Entity)
まずは、ユーザー情報を一つのオブジェクトとして扱うためのクラスを作成します。 このように、データベースの1行(レコード)を表現するクラスを「Entity」や「DTO」と呼びます。
package model;
import java.io.Serializable;
/**
* ユーザー情報を保持するエンティティクラス
*/
public class UserEntity implements Serializable {
private String name;
private int age;
public UserEntity() {}
public UserEntity(String name, int age) {
this.name = name;
this.age = age;
}
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; }
}
2. データベース操作クラス(DAO)
次に、SQLを実行してデータベースとのやり取りを専門に行うDAO(Data Access Object)を作成します。
このクラスの中で、先ほど解説したPreparedStatementを使用し、安全にデータを挿入します。
package dao;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import model.UserEntity;
/**
* データベースへの保存処理を担うクラス
*/
public class UserDAO {
// データベース接続情報(環境に合わせて適宜変更してください)
private final String URL = "jdbc:mysql://localhost:3306/sample_db?useSSL=false&serverTimezone=UTC";
private final String USER = "db_user";
private final String PASS = "db_password";
/**
* ユーザーをデータベースに登録するメソッド
* @param user 登録するユーザー情報
* @return 登録に成功した場合はtrue
*/
public boolean register(UserEntity user) {
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
// try-with-resourcesを使用してリソースを確実に解放する
try (Connection conn = DriverManager.getConnection(URL, USER, PASS);
PreparedStatement pstmt = conn.prepareStatement(sql)) {
pstmt.setString(1, user.getName());
pstmt.setInt(2, user.getAge());
int result = pstmt.executeUpdate();
return result > 0;
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
}
3. 司令塔となるServletクラス
最後に、画面からのリクエストを受け取り、DAOを呼び出して処理を完結させるServletを作成します。
package controller;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import dao.UserDAO;
import model.UserEntity;
/**
* 登録処理のメインコントローラー
*/
@WebServlet("/register")
public class RegisterServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1. リクエストの文字コード設定
request.setCharacterEncoding("UTF-8");
// 2. パラメータの取得
String name = request.getParameter("userName");
String ageStr = request.getParameter("userAge");
// 3. 入力値の簡易チェック
if (name == null || name.isEmpty() || ageStr == null || ageStr.isEmpty()) {
response.sendRedirect("error.jsp");
return;
}
try {
int age = Integer.parseInt(ageStr);
// 4. エンティティの作成とDAOの実行
UserEntity user = new UserEntity(name, age);
UserDAO dao = new UserDAO();
if (dao.register(user)) {
// 登録成功時は完了画面へ
response.sendRedirect("success.jsp");
} else {
// DBエラー時はエラー画面へ
response.sendRedirect("error.jsp");
}
} catch (NumberFormatException e) {
// 数値変換エラー時の処理
response.sendRedirect("error.jsp");
}
}
}
8. データベース操作における例外処理とリソース解放(try-with-resources)
Javaでデータベースを扱う際、プログラムが正常に終了したかどうかに関わらず、「必ず行わなければならない作業」があります。それがリソースの解放(クローズ処理)です。 データベースへの接続(Connection)、命令の実行(Statement)、結果の取得(ResultSet)といったオブジェクトは、コンピュータのメモリやネットワークの接続枠を消費します。これらを使い終わった後に放置すると、「メモリリーク」や「接続上限エラー」を引き起こし、最終的にはサーバーがダウンしてしまいます。
昔ながらの書き方と現代的な書き方
以前のJava(Java 6以前)では、finallyブロックの中で一つずつclose()メソッドを呼び出す必要がありました。しかし、この方法はコードが非常に複雑になりやすく、クローズ漏れが発生する原因にもなっていました。
Java 7から導入されたtry-with-resources構文を利用することで、この問題は劇的に改善されました。tryの直後にある小括弧()の中で宣言されたリソースは、処理が正常に終わった場合でも、例外が発生して中断された場合でも、自動的にクローズされることが保証されます。
安全なリソース管理の仕組み
この構文が使えるのは、java.lang.AutoCloseableインターフェースを実装しているクラスに限られますが、JDBCの主要なクラス(Connection, Statement, PreparedStatement, ResultSet)はすべてこれを実装しています。
そのため、データベース操作を行う際は、必ずこの構文を使うようにしましょう。
// 自動クローズの対象となるオブジェクトをtryの()内で生成
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql)) {
// ここでDB操作を行う
pstmt.executeUpdate();
} catch (SQLException e) {
// 例外が発生した場合はここを通るが、リソースは既に閉じられている
logger.error("DB接続エラー", e);
}
// tryを抜けた時点でconnとpstmtは自動的にcloseされている
この仕組みを徹底することで、接続が切れないまま残り続ける「ゾンビ・コネクション」を防ぎ、安定したシステム運用が可能になります。 初心者の方が陥りやすい「最初は動いていたのに、時間が経つと動かなくなる」という現象の多くは、このクローズ漏れが原因です。
9. ServletでDB保存を行う際によくあるエラーと解決策
開発を進めていると、必ずと言っていいほどエラーに直面します。特にServletとJDBCの連携は、Webサーバー、Java、データベースという3つの異なる要素が関わるため、原因の特定が難しいことがあります。 ここでは、初心者が遭遇しやすい代表的なエラーとその対処法をまとめました。
1. java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
これは、Javaがデータベースを動かすための「ドライバ」を見つけられない時に発生します。
- 原因: JDBCドライバ(MySQLならmysql-connector-java-xxx.jar)がプロジェクトのビルドパス、またはWebサーバーの
WEB-INF/libフォルダに入っていない。 - 解決策: 公式サイトからドライバのJARファイルをダウンロードし、プロジェクトのライブラリに追加してください。Eclipse等のIDEを使用している場合は、デプロイメント・アセンブリの設定も確認が必要です。
2. java.sql.SQLException: Access denied for user...
データベースへのログインに失敗した際のエラーです。
- 原因: 接続URL、ユーザー名、パスワードのいずれかが間違っている。あるいは、そのユーザーにデータベースを操作する権限が与えられていない。
- 解決策: 接続情報を今一度確認してください。特に本番環境と開発環境で情報が異なる場合に発生しやすいため、設定ファイル等に切り出している場合はその内容を精査しましょう。
3. java.lang.NumberFormatException: For input string: ""
数値を扱う処理で頻発するエラーです。
- 原因: フォームから送られてきた文字列が空("")や数値以外の文字を含んでいる状態で、
Integer.parseInt()を実行した。 - 解決策: 文字列を数値に変換する前に、必ず「nullチェック」と「空文字チェック」を行いましょう。また、クライアント側のHTMLでも
type="number"やrequired属性を使い、不正な値が送られない工夫をすることが重要です。
4. 文字化け(日本語が ??? になる)
厳密にはエラーではありませんが、非常に多いトラブルです。
- 原因: Servletでの文字エンコーディング指定が不足している、またはデータベース側の文字コード設定がUTF-8になっていない。
- 解決策: Servletの
doPostメソッドの先頭で、request.setCharacterEncoding("UTF-8")を呼び出してください。また、JDBCの接続URLにuseUnicode=true&characterEncoding=UTF-8を追記するのも効果的です。
5. 接続タイムアウトや通信エラー
ネットワークやサーバーの状態に起因するものです。
- 原因: データベースサーバーが起動していない、ファイアウォールで接続が遮断されている、または接続URLのIPアドレスやポート番号が間違っている。
- 解決策: データベース管理ツール(MySQL Workbench等)から同じ設定で接続できるかテストし、ネットワーク的な疎通が取れているかを確認してください。
エラーが発生したときは、コンソールに出力される「スタックトレース」を上から順に読み解くことが解決への近道です。 「どこで」「何が」起きたのかを正確に把握することで、デバッグの効率は飛躍的に向上します。