
サーブレット&JSPの基本|DAOを使ったWebアプリのデータベース化
投稿を一時保存から永続保存へ。DAOを使って、ひとこと広場をサーバ再起動後もデータが残るWebアプリに進化させよう
これまで作成してきた「ひとこと広場」では、投稿されたひとことをアプリケーションスコープに保存していました。
アプリケーションスコープを使うと、Webアプリケーションが起動している間は、複数の利用者で同じ投稿一覧を共有できます。
たとえば、山田さんが投稿したひとことを、佐藤さんがメイン画面で確認する、といった動きができます。
これは、スコープの働きやServletとJSPの連携を学ぶには分かりやすい方法です。
しかし、アプリケーションスコープには大きな弱点があります。
それは、サーバを停止したり、Webアプリケーションを再起動したりすると、保存していた投稿データが消えてしまうことです。
学習用のアプリならそれでも確認できますが、実際のWebアプリでは、投稿内容や会員情報、注文履歴などはサーバを再起動しても残っている必要があります。
そこで今回は、「ひとこと広場」をデータベース対応版に改良します。
投稿の保存先を、アプリケーションスコープのmessageListから、H2 DatabaseのHirobaデータベースにあるMESSAGESテーブルへ変更します。
この変更により、投稿されたひとことはデータベースに保存され、サーバを再起動しても残るようになります。
また、データベースアクセスにはDAOパターンを使います。
DAOを使うと、SQLの実行やConnection、PreparedStatement、ResultSetなどのJDBC特有の処理を専用クラスにまとめられます。
MainサーブレットやJSPに直接SQLを書かず、データベース操作はMessagesDAOに任せる構成にします。
この記事では、ひとこと広場をデータベース化するための準備、処理の流れ、ShortMessageクラスの変更、MessagesDAOの作成、PostMessageLogicとGetMessageListLogicの作成・変更、Mainサーブレットの修正、そしてコネクションプーリングの考え方まで順番に解説します。
ひとこと広場をデータベース化する理由
これまでのひとこと広場では、投稿一覧をmessageListという名前でアプリケーションスコープに保存していました。
この方法では、Webアプリケーションが動いている間だけ投稿を保持できます。
しかし、サーバを停止すると、アプリケーションスコープに保存されていたmessageListは消えてしまいます。
保存先による違い
| 保存先 | 特徴 | サーバ停止後 |
|---|---|---|
| アプリケーションスコープ | Webアプリケーション全体で共有できる | データが消える |
| データベース | データを外部に保存できる | データが残る |
今回の改良では、投稿をデータベースに保存します。
データベースに保存しておけば、サーバを再起動したあとでも、保存済みの投稿を再び取得して表示できます。
これにより、ひとこと広場はより実践的なWebアプリケーションに近づきます。
データベース化で準備するもの
ひとこと広場をデータベース対応にするため、まずH2 Database側にHirobaデータベースを用意します。
その中に、投稿を保存するMESSAGESテーブルを作成します。
H2 Database側の準備
| 順番 | 作業 | 内容 |
|---|---|---|
| 1 | Hirobaデータベースを作成する | ひとこと広場用のデータベースを用意する |
| 2 | MESSAGESテーブルを作成する | 投稿を保存するテーブルを用意する |
| 3 | 初期レコードを追加する | 動作確認用の投稿データを登録する |
| 4 | JDBCドライバを配置する | HirobaプロジェクトのWEB-INF/libにH2のJARを配置する |
Hirobaプロジェクト側の準備
| 項目 | 内容 |
|---|---|
| プロジェクト名 | Hiroba |
| データベース名 | Hiroba |
| JDBCドライバ配置先 | src/main/webapp/WEB-INF/lib |
| 使用するJSP | EL式とJSTLを用いたmain.jsp |
H2 Databaseに接続するためには、H2のJDBCドライバが必要です。
すでにHirobaプロジェクトのsrc/main/webapp/WEB-INF/libにh2-x.x.x.jarを配置している場合は、そのまま利用できます。
配置していない場合は、H2 DatabaseのJDBCドライバをWEB-INF/libに配置しておきます。
3つのファイルを配置していない場合
src/main/webapp/WEB-INF/lib にJSTL APIとJSTL実装の2つのJARファイルを配置します。
src/main/webapp/WEB-INF/lib にJDBCドライバを配置します。

下図のように配置されていれば、大丈夫です。

MESSAGESテーブルの構成
ひとこと広場の投稿を保存するテーブルとして、MESSAGESテーブルを作成します。
MESSAGESテーブルは、投稿1件を1行のレコードとして保存します。
MESSAGESテーブル
| ID | USER_NAME | TEXT |
|---|---|---|
| 1 | 山田 太郎 | Javaの復習をしました |
| 2 | 佐藤 花子 | JSPのEL式が分かってきました |
列の意味
| 列名 | 内容 |
|---|---|
| ID | 投稿を識別する番号 |
| USER_NAME | 投稿した利用者名 |
| TEXT | 投稿されたひとことの本文 |
IDは、投稿を一意に識別するための番号です。
USER_NAMEには、ログイン中の利用者名を保存します。
TEXTには、利用者が入力した投稿内容を保存します。
IDは自動連番にするため、新しい投稿を追加するときにJavaプログラム側でIDを指定する必要はありません。
図1:ひとこと広場用のHirobaデータベースとMESSAGESテーブル

この図から分かること
ひとこと広場をデータベース化すると、投稿データの保存先がアプリケーションスコープからHirobaデータベースのMESSAGESテーブルに変わります。
アプリケーションスコープに保存した投稿はサーバ停止で消えてしまいます。
一方、MESSAGESテーブルに保存した投稿は、サーバを再起動しても残ります。
そのため、投稿内容を継続的に保存したい場合は、データベースを使う構成が適しています。
Hirobaデータベースを作成する
まず、H2コンソールを起動して、Hirobaデータベースを作成します。

H2コンソールの保存済み設定でGeneric H2 (Embedded)を選択します。
画面上部のプルダウンメニューから日本語を選択すると、日本語表示に切り替えられます。
JDBC URLには、次の値を入力して「接続」をクリックします。
jdbc:h2:~/Hiroba

組み込みモードでは、JDBC URLで指定したデータベースが存在しない場合、新しく作成されます。すでにHirobaデータベースが存在している場合は、そのデータベースへ接続します。
エラーが出る場合は、H2 Databaseを終了させてから、H2コンソールを起動します。

Hirobaデータベース作成の手順
| 手順 | 内容 |
|---|---|
| 1 | H2コンソールを起動する |
| 2 | 保存済み設定でGeneric H2 (Embedded)を選択する |
| 3 | JDBC URLにjdbc:h2:~/Hirobaを入力する |
| 4 | 接続ボタンをクリックする |
| 5 | Hirobaデータベースが作成される |
| 6 | 作成を確認したら切断ボタンをクリックする |
データベースの作成が確認できたら、左上の切断ボタンで接続を解除します。

このあとのテーブル作成やデータ追加では、サーバーモードで接続します。
サーバーモードでHirobaデータベースに接続する
Hirobaデータベースを作成したら、次にサーバーモードで接続します。
H2コンソールの保存済み設定でGeneric H2 (Server)を選択します。
JDBC URLには、次の値を指定します。
jdbc:h2:tcp://localhost/~/Hiroba

サーバーモード接続の設定
| 項目 | 設定値 |
|---|---|
| 保存済み設定 | Generic H2 (Server) |
| JDBC URL | jdbc:h2:tcp://localhost/~/Hiroba |
| 接続先 | 作成済みのHirobaデータベース |
| 用途 | テーブル作成、初期データ追加、Javaプログラムからの利用 |
サーバーモードでは、複数のプログラムからデータベースへアクセスしやすくなります。
今後、HirobaプロジェクトのMessagesDAOから接続するときも、このサーバーモード用のJDBC URLを使います。
MESSAGESテーブルを作成するSQL
H2コンソールでHirobaデータベースに接続できたら、SQL入力欄に次のSQLを入力して実行します。
CREATE TABLE MESSAGES (
ID INT PRIMARY KEY AUTO_INCREMENT,
USER_NAME VARCHAR(100) NOT NULL,
TEXT VARCHAR(255) NOT NULL
);このSQLを実行すると、HirobaデータベースにMESSAGESテーブルが作成されます。

SQLの内容
| SQLの部分 | 内容 |
|---|---|
| CREATE TABLE MESSAGES | MESSAGESテーブルを作成する |
| ID INT PRIMARY KEY AUTO_INCREMENT | ID列を主キーにし、自動連番にする |
| USER_NAME VARCHAR(100) NOT NULL | 投稿者名を最大100文字で保存し、必須にする |
| TEXT VARCHAR(255) NOT NULL | 投稿内容を最大255文字で保存し、必須にする |
IDはAUTO_INCREMENTを指定しているため、レコードを追加するたびに自動で番号が増えます。
そのため、INSERT文ではUSER_NAMEとTEXTだけを指定します。
MESSAGESテーブルに初期データを追加するSQL
MESSAGESテーブルを作成したら、動作確認用の初期データを追加します。
INSERT INTO MESSAGES (USER_NAME, TEXT)
VALUES ('山田 太郎', 'Javaの復習をしました');
INSERT INTO MESSAGES (USER_NAME, TEXT)
VALUES ('佐藤 花子', 'JSPのEL式が分かってきました');このSQLを実行すると、MESSAGESテーブルに2件の投稿データが追加されます。

追加されるデータ
| ID | USER_NAME | TEXT |
|---|---|---|
| 1 | 山田 太郎 | Javaの復習をしました |
| 2 | 佐藤 花子 | JSPのEL式が分かってきました |
IDは自動連番なので、INSERT文では指定していません。
SQLの実行が終わったら、H2コンソールの切断ボタンをクリックして接続を解除します。

データベース化で変更・追加するクラス
ひとこと広場をデータベース対応版にするため、既存クラスの変更と新規クラスの作成を行います。
変更・追加するクラス
| クラス | 区分 | 内容 |
|---|---|---|
| ShortMessage.java | 既存変更 | MESSAGESテーブルのレコードを格納できるようにidフィールドを追加する |
| Main.java | 既存変更 | ひとこと一覧の取得と投稿処理をデータベース対応に変更する |
| PostMessageLogic.java | 既存変更 | ひとことの保存先をアプリケーションスコープからデータベースに変更する |
| GetMessageListLogic.java | 新規作成 | 全ひとことをデータベースから取得する |
| MessagesDAO.java | 新規作成 | MESSAGESテーブルを担当し、全レコード取得とレコード追加を行う |
これまでのひとこと広場では、MainサーブレットがアプリケーションスコープからmessageListを取得し、PostMessageLogicがそのリストへ投稿を追加していました。
データベース対応版では、アプリケーションスコープを使った投稿管理をやめます。
投稿一覧はデータベースからSELECT文で取得し、新しい投稿はINSERT文でデータベースに追加します。
図2:ひとこと一覧の取得と投稿の流れ

この図から分かること
ひとこと一覧を表示するとき、Mainサーブレットは直接データベースへアクセスしません。
MainサーブレットはGetMessageListLogicを呼び出します。
GetMessageListLogicはMessagesDAOのfindAllメソッドを呼び出し、MessagesDAOがMESSAGESテーブルにSELECT文を実行します。
投稿するときは、MainサーブレットがShortMessageインスタンスを作成し、PostMessageLogicへ渡します。
PostMessageLogicはMessagesDAOのcreateメソッドを呼び出し、MessagesDAOがMESSAGESテーブルにINSERT文を実行します。
ひとことリスト取得の流れ
ひとこと一覧を表示するときは、データベースから投稿一覧を取得します。
ひとことリスト取得の手順
| 順番 | 処理 |
|---|---|
| 1 | MainサーブレットがGetMessageListLogicのexecuteメソッドを呼び出す |
| 2 | GetMessageListLogicがMessagesDAOのfindAllメソッドを呼び出す |
| 3 | MessagesDAOがMESSAGESテーブルにSELECT文を実行する |
| 4 | MessagesDAOがResultSetからShortMessageインスタンスを作成する |
| 5 | ShortMessageインスタンスをListに追加する |
| 6 | ListをGetMessageListLogicへ返す |
| 7 | GetMessageListLogicがListをMainサーブレットへ返す |
| 8 | MainサーブレットがmessageListとしてリクエストスコープに保存する |
| 9 | main.jspがEL式とJSTLで一覧表示する |
この流れにより、main.jspはデータベースを直接意識せず、これまでと同じようにmessageListを表示できます。
ひとこと投稿の流れ
投稿フォームからひとことが送信された場合は、入力内容をデータベースへ追加します。
ひとこと投稿の手順
| 順番 | 処理 |
|---|---|
| 1 | MainサーブレットがPOSTリクエストを受け取る |
| 2 | リクエストパラメータtextを取得する |
| 3 | セッションスコープからloginUserを取得する |
| 4 | loginUserの名前と入力内容からShortMessageを作成する |
| 5 | MainサーブレットがPostMessageLogicのexecuteメソッドを呼び出す |
| 6 | PostMessageLogicがMessagesDAOのcreateメソッドを呼び出す |
| 7 | MessagesDAOがMESSAGESテーブルにINSERT文を実行する |
| 8 | 投稿後の一覧を再取得する |
| 9 | main.jspへフォワードする |
これまでのように、Listにaddしてメモリ上に投稿を追加するのではありません。
データベース対応版では、INSERT文でMESSAGESテーブルに投稿を追加します。
ShortMessageクラスをデータベース対応に変更する
まず、ひとことの情報を表すShortMessageクラスを変更します。
これまでのShortMessageクラスは、userNameとtextを持っていました。
データベース対応版では、MESSAGESテーブルのID列にも対応できるように、idフィールドを追加します。
ShortMessageクラスのフィールド
| フィールド | 対応する列 | 内容 |
|---|---|---|
| id | ID | 投稿ID |
| userName | USER_NAME | 投稿者名 |
| text | TEXT | 投稿内容 |
新しく投稿するときは、まだIDが決まっていません。
そのため、userNameとtextだけを受け取るコンストラクタを用意します。
一方、データベースから取得した投稿をShortMessageインスタンスにするときは、id、userName、textを受け取るコンストラクタを使います。
MESSAGESテーブルのレコードを表すクラスの例
ファイル名: ShortMessage.java
package model;
import java.io.Serializable;
public class ShortMessage implements Serializable {
private static final long serialVersionUID = 1L;
private int id;
private String userName;
private String text;
public ShortMessage() {
}
public ShortMessage(String userName, String text) {
this.userName = userName;
this.text = text;
}
public ShortMessage(int id, String userName, String text) {
this.id = id;
this.userName = userName;
this.text = text;
}
public int getId() {
return id;
}
public String getUserName() {
return userName;
}
public String getText() {
return text;
}
}ShortMessage.javaは、modelパッケージに作成します。
このクラスは、MESSAGESテーブルの1行分のデータをJavaで扱うためのクラスです。
idは投稿ID、userNameは投稿者名、textは投稿内容を表します。
引数なしのコンストラクタは、JavaBeansとして扱いやすくするために用意しています。
userNameとtextを受け取るコンストラクタは、新規投稿時に使います。
id、userName、textを受け取るコンストラクタは、データベースから取得したレコードをShortMessageインスタンスに変換するときに使います。
MessagesDAOを新しく作成する
次に、MESSAGESテーブルを担当するMessagesDAOを作成します。
MessagesDAOは、データベースアクセスを担当するDAOクラスです。
このクラスには、全投稿を取得するfindAllメソッドと、新しい投稿を追加するcreateメソッドを用意します。
MessagesDAOの役割
| メソッド | 内容 |
|---|---|
| findAll | MESSAGESテーブルの全レコードを取得する |
| create | MESSAGESテーブルに新しいレコードを追加する |
findAllメソッドは、MESSAGESテーブルにSELECT文を実行し、取得したレコードをShortMessageインスタンスとしてListに詰め替えます。
createメソッドは、MESSAGESテーブルにINSERT文を実行し、新しい投稿を追加します。
MESSAGESテーブルを担当するDAOの例
ファイル名: MessagesDAO.java
package dao;
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 model.ShortMessage;
public class MessagesDAO {
private final String JDBC_URL =
"jdbc:h2:tcp://localhost/~/Hiroba";
private final String DB_USER = "sa";
private final String DB_PASS = "";
public List<ShortMessage> findAll() {
List<ShortMessage> messageList = new ArrayList<>();
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
"JDBCドライバを読み込めませんでした");
}
try (Connection conn = DriverManager.getConnection(
JDBC_URL, DB_USER, DB_PASS)) {
String sql =
"SELECT ID, USER_NAME, TEXT FROM MESSAGES ORDER BY ID DESC";
PreparedStatement pStmt = conn.prepareStatement(sql);
ResultSet rs = pStmt.executeQuery();
while (rs.next()) {
int id = rs.getInt("ID");
String userName = rs.getString("USER_NAME");
String text = rs.getString("TEXT");
ShortMessage message =
new ShortMessage(id, userName, text);
messageList.add(message);
}
} catch (SQLException e) {
e.printStackTrace();
return null;
}
return messageList;
}
public boolean create(ShortMessage message) {
try {
Class.forName("org.h2.Driver");
} catch (ClassNotFoundException e) {
throw new IllegalStateException(
"JDBCドライバを読み込めませんでした");
}
try (Connection conn = DriverManager.getConnection(
JDBC_URL, DB_USER, DB_PASS)) {
String sql =
"INSERT INTO MESSAGES (USER_NAME, TEXT) VALUES (?, ?)";
PreparedStatement pStmt = conn.prepareStatement(sql);
pStmt.setString(1, message.getUserName());
pStmt.setString(2, message.getText());
int result = pStmt.executeUpdate();
if (result != 1) {
return false;
}
} catch (SQLException e) {
e.printStackTrace();
return false;
}
return true;
}
}MessagesDAO.javaは、daoパッケージに作成します。
JDBC_URLには、Hirobaデータベースへサーバーモードで接続するためのJDBC URLを指定しています。
findAllメソッドでは、MESSAGESテーブルからID、USER_NAME、TEXTを取得します。
ORDER BY ID DESCを付けているため、新しい投稿ほど先に取得されます。
取得したレコードは、ShortMessageインスタンスに変換してmessageListに追加します。
createメソッドでは、INSERT文を使ってMESSAGESテーブルに新しい投稿を追加します。
SQL文の?には、PreparedStatementのsetStringで値を設定します。
1番目の?には投稿者名、2番目の?には投稿内容を設定します。
executeUpdateを実行すると、追加された行数が戻り値として返ります。
戻り値が1であれば、1件の投稿が正しく追加されたと判断できます。
MessagesDAOのfindAllメソッドの処理
findAllメソッドは、投稿一覧をデータベースから取得する処理です。
findAllメソッドの流れ
| 順番 | 処理 | 内容 |
|---|---|---|
| 1 | Listを作成する | ShortMessageを入れるmessageListを用意する |
| 2 | JDBCドライバを読み込む | org.h2.Driverを読み込む |
| 3 | データベースへ接続する | Hirobaデータベースへ接続する |
| 4 | SELECT文を準備する | MESSAGESテーブルから全投稿を取得する |
| 5 | SELECT文を実行する | ResultSetを取得する |
| 6 | ResultSetを1行ずつ読む | rs.nextで次のレコードへ進む |
| 7 | 列の値を取得する | ID、USER_NAME、TEXTを取り出す |
| 8 | ShortMessageを作成する | 取得した値をインスタンスに入れる |
| 9 | Listへ追加する | messageListにShortMessageを追加する |
| 10 | Listを返す | 呼び出し元へ投稿一覧を返す |
この処理により、MESSAGESテーブルの投稿データをJavaのListとして扱えるようになります。
MainサーブレットやJSPは、ResultSetを直接扱う必要がありません。
MessagesDAOのcreateメソッドの処理
createメソッドは、新しい投稿をデータベースへ追加する処理です。
createメソッドの流れ
| 順番 | 処理 | 内容 |
|---|---|---|
| 1 | JDBCドライバを読み込む | org.h2.Driverを読み込む |
| 2 | データベースへ接続する | Hirobaデータベースへ接続する |
| 3 | INSERT文を準備する | USER_NAMEとTEXTを追加するSQLを用意する |
| 4 | ?に値を設定する | 投稿者名と投稿内容を設定する |
| 5 | INSERT文を実行する | executeUpdateでレコードを追加する |
| 6 | 追加件数を確認する | 1件追加されたか確認する |
| 7 | 結果を返す | 成功ならtrue、失敗ならfalseを返す |
createメソッドを用意しておくと、PostMessageLogicはSQLを書かずに投稿を保存できます。
データベースへINSERTする具体的な処理は、MessagesDAOに任せます。
図3:MessagesDAOが投稿の取得と追加を担当する

この図から分かること
MessagesDAOは、MESSAGESテーブルへのデータベースアクセスを担当するクラスです。
投稿一覧を取得するときは、findAllメソッドでSELECT文を実行します。
新しい投稿を追加するときは、createメソッドでINSERT文を実行します。
MainサーブレットやJSPには、Connection、PreparedStatement、ResultSet、SQL文などを書きません。
JDBC特有の処理をMessagesDAOにまとめることで、役割分担が分かりやすくなります。
PostMessageLogicをDAO対応に変更する
これまでのPostMessageLogicは、アプリケーションスコープ上のmessageListにShortMessageを追加する役割でした。
データベース対応版では、messageListを引数で受け取る必要はありません。
PostMessageLogicは、ShortMessageインスタンスを受け取り、MessagesDAOのcreateメソッドを呼び出します。
PostMessageLogicの変更点
| 項目 | これまで | データベース対応後 |
|---|---|---|
| 保存先 | アプリケーションスコープのmessageList | MESSAGESテーブル |
| 引数 | messageListとShortMessage | ShortMessageのみ |
| 追加方法 | Listの先頭に追加 | MessagesDAOのcreateを呼び出す |
| SQL | 使わない | MessagesDAOの中でINSERT文を実行する |
PostMessageLogicは、投稿するという業務処理を担当します。
実際のデータベース追加処理はMessagesDAOに任せます。
ひとことの投稿に関する処理をするモデルの例
ファイル名: PostMessageLogic.java
package model;
import dao.MessagesDAO;
public class PostMessageLogic {
public void execute(ShortMessage message) {
MessagesDAO dao = new MessagesDAO();
dao.create(message);
}
}PostMessageLogic.javaは、modelパッケージに作成します。
executeメソッドは、ShortMessageインスタンスを受け取ります。
そして、MessagesDAOを作成し、createメソッドを呼び出します。
これにより、投稿内容がMESSAGESテーブルに保存されます。
PostMessageLogic自身は、ConnectionやPreparedStatement、INSERT文を直接扱いません。
データベースアクセスの詳細はMessagesDAOに任せています。
GetMessageListLogicを新しく作成する
次に、投稿一覧を取得するモデルクラスを作成します。
このクラスがGetMessageListLogicです。
GetMessageListLogicは、MessagesDAOのfindAllメソッドを呼び出し、MESSAGESテーブルの全投稿を取得します。
GetMessageListLogicの役割
| 項目 | 内容 |
|---|---|
| クラス名 | GetMessageListLogic |
| パッケージ | model |
| 役割 | 投稿一覧を取得する |
| 呼び出すDAO | MessagesDAO |
| 呼び出すメソッド | findAll |
| 戻り値 | List |
Mainサーブレットは、投稿一覧を表示したいときにGetMessageListLogicを呼び出します。
GetMessageListLogicはMessagesDAOに処理を依頼し、取得したListをMainサーブレットへ返します。
ひとことの取得に関する処理をするモデルの例
ファイル名: GetMessageListLogic.java
package model;
import java.util.List;
import dao.MessagesDAO;
public class GetMessageListLogic {
public List<ShortMessage> execute() {
MessagesDAO dao = new MessagesDAO();
List<ShortMessage> messageList = dao.findAll();
return messageList;
}
}GetMessageListLogic.javaは、modelパッケージに作成します。
executeメソッドでは、MessagesDAOを作成し、findAllメソッドを呼び出しています。
findAllメソッドから返ってきたListを、そのまま呼び出し元へ返します。
このクラスも、SQLやConnectionを直接扱いません。
投稿一覧を取得する処理の入口になり、実際のデータベースアクセスはMessagesDAOに任せます。
Mainサーブレットをデータベース対応に変更する
最後に、Mainサーブレットを変更します。
これまでのMainサーブレットでは、アプリケーションスコープに保存されたmessageListを使っていました。
データベース対応版では、アプリケーションスコープを使った投稿管理を削除します。
代わりに、GetMessageListLogicを使ってデータベースから投稿一覧を取得し、リクエストスコープに保存します。
投稿が送信された場合は、PostMessageLogicを使ってデータベースに追加します。
Mainサーブレットの変更点
| 処理 | これまで | データベース対応後 |
|---|---|---|
| 投稿一覧の保存先 | アプリケーションスコープ | HirobaデータベースのMESSAGESテーブル |
| 投稿一覧の取得 | application.getAttribute | GetMessageListLogicを呼び出す |
| 投稿の追加 | messageListへ追加 | PostMessageLogicを呼び出してINSERT |
| JSPへ渡す方法 | applicationスコープのmessageListを参照 | request.setAttributeでmessageListを渡す |
| アプリケーションスコープ | 投稿保存に使用する | 投稿保存には使用しない |
Mainサーブレットは、画面表示に必要な投稿一覧をデータベースから取得します。
そして、JSPへフォワードする前に、request.setAttributeでmessageListをリクエストスコープに保存します。
main.jspは、EL式とJSTLでmessageListを表示できます。
ひとことに関するリクエストを処理するコントローラの例
ファイル名: Main.java
package servlet;
import java.io.IOException;
import java.util.List;
import jakarta.servlet.RequestDispatcher;
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 jakarta.servlet.http.HttpSession;
import model.GetMessageListLogic;
import model.PostMessageLogic;
import model.ShortMessage;
import model.User;
@WebServlet("/Main")
public class Main extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// ログインしているか確認するため、
// セッションスコープからユーザー情報を取得
HttpSession session = request.getSession();
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null) {
// ログインしていない場合はトップ画面へリダイレクト
response.sendRedirect("index.jsp");
return;
}
// ひとことリストをデータベースから取得し、
// リクエストスコープに保存
GetMessageListLogic getMessageListLogic =
new GetMessageListLogic();
List<ShortMessage> messageList =
getMessageListLogic.execute();
request.setAttribute("messageList", messageList);
// メイン画面にフォワード
RequestDispatcher dispatcher =
request.getRequestDispatcher("WEB-INF/jsp/main.jsp");
dispatcher.forward(request, response);
}
protected void doPost(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
// 文字コードの指定
request.setCharacterEncoding("UTF-8");
// セッションスコープからユーザー情報を取得
HttpSession session = request.getSession();
User loginUser = (User) session.getAttribute("loginUser");
if (loginUser == null) {
response.sendRedirect("index.jsp");
return;
}
// リクエストパラメータの取得
String text = request.getParameter("text");
// 入力値チェック
if (text != null && text.length() != 0) {
// ひとことを作成してデータベースに追加
ShortMessage message =
new ShortMessage(loginUser.getName(), text);
PostMessageLogic postMessageLogic =
new PostMessageLogic();
postMessageLogic.execute(message);
} else {
// エラーメッセージをリクエストスコープに保存
request.setAttribute("errorMsg",
"ひとことが入力されていません");
}
// 投稿後のひとことリストをデータベースから取得し、
// リクエストスコープに保存
GetMessageListLogic getMessageListLogic =
new GetMessageListLogic();
List<ShortMessage> messageList =
getMessageListLogic.execute();
request.setAttribute("messageList", messageList);
// メイン画面にフォワード
RequestDispatcher dispatcher =
request.getRequestDispatcher("WEB-INF/jsp/main.jsp");
dispatcher.forward(request, response);
}
}Main.javaは、servletパッケージに配置します。
doGetでは、まずセッションスコープからloginUserを取得します。
ログインしていない場合は、index.jspへリダイレクトします。
ログイン済みの場合は、GetMessageListLogicを使ってデータベースから投稿一覧を取得し、messageListという属性名でリクエストスコープに保存します。
その後、WEB-INF/jsp/main.jspへフォワードします。
doPostでは、投稿フォームから送信されたtextを取得します。
textが空でなければ、ログインユーザー名と投稿内容を使ってShortMessageインスタンスを作成し、PostMessageLogicに投稿処理を依頼します。
textが空の場合は、errorMsgをリクエストスコープに保存します。
最後に、投稿後の最新一覧をGetMessageListLogicで取得し、main.jspへフォワードします。
main.jsp側の表示は大きく変えなくてよい
今回の変更では、main.jspの表示方法は大きく変える必要がありません。
MainサーブレットがmessageListという属性名で投稿一覧をリクエストスコープに保存するため、JSP側ではこれまでと同じようにEL式とJSTLで表示できます。
main.jspで利用する属性
| 属性名 | 保存先 | 内容 |
|---|---|---|
| loginUser | セッションスコープ | ログイン中のユーザー情報 |
| messageList | リクエストスコープ | データベースから取得した投稿一覧 |
| errorMsg | リクエストスコープ | 入力エラー時のメッセージ |
main.jspでは、messageListをc:forEachで繰り返し表示します。
投稿者名や投稿内容は、c:outを使って出力します。
投稿内容は利用者が入力する値なので、HTMLとして特別な意味を持つ文字を安全に扱うためにも、c:outで出力するのが適しています。
データベース対応後の保存処理の違い
今回の改良で、ひとことの保存方法が大きく変わります。
これまでは、PostMessageLogicがmessageListへShortMessageを追加していました。
データベース対応後は、PostMessageLogicがMessagesDAOを呼び出し、MessagesDAOがMESSAGESテーブルへINSERT文を実行します。
保存処理の違い
| 項目 | これまで | データベース対応後 |
|---|---|---|
| 投稿先 | messageList | MESSAGESテーブル |
| 保存場所 | アプリケーションスコープ | H2 DatabaseのHirobaデータベース |
| 追加処理 | Listへ追加 | INSERT文 |
| 取得処理 | application.getAttribute | SELECT文 |
| サーバ再起動後 | 投稿が消える | 投稿が残る |
この変更により、ひとこと広場はサーバを再起動しても投稿が残るWebアプリケーションになります。
DAO化したときの役割分担
DAOを使うと、クラスごとの役割が整理されます。
クラスごとの役割
| クラス | 役割 |
|---|---|
| Main | リクエストを受け取り、ログイン確認、モデル呼び出し、フォワードを行う |
| GetMessageListLogic | 投稿一覧を取得する処理を担当する |
| PostMessageLogic | 投稿を保存する処理を担当する |
| MessagesDAO | MESSAGESテーブルへのSELECT文とINSERT文を担当する |
| ShortMessage | MESSAGESテーブルの1件分のデータを表す |
| main.jsp | EL式とJSTLで投稿一覧を表示する |
Mainサーブレットは、直接SQLを書きません。
JSPにもSQLやDAO呼び出しを書きません。
データベース操作はMessagesDAOに集め、投稿一覧の取得や投稿保存の入口はmodelパッケージのLogicクラスに任せます。
このように役割を分けると、Servlet、Model、DAO、JSPの責任が分かりやすくなります。
動作確認
ブラウザで http://localhost:8080/Hiroba/ へアクセスします。
下図のように表示されれば、データベースから値が読み込めています。

コネクションプーリングとは
今回のMessagesDAOでは、findAllメソッドやcreateメソッドが呼び出されるたびに、Hirobaデータベースへ接続しています。
処理が終わると、try-with-resourcesによってConnectionが閉じられます。
学習用の小さなアプリケーションでは、この方法でJDBCの基本を理解しやすくなります。
ただし、本格的なWebアプリケーションでは、リクエストのたびに接続と切断を繰り返すと、アプリケーションサーバの負荷が大きくなる場合があります。
そこで使われるのが、コネクションプーリングです。
コネクションプーリングとは、あらかじめ複数のデータベース接続を作ってプールしておき、必要なときに空いている接続を貸し出す仕組みです。
使い終わった接続は完全に破棄せず、プールに返します。
これにより、リクエストのたびに接続の確立と切断を行う負荷を減らせます。
コネクションプーリングによる接続の使い回し
コネクションプーリングでは、データベース接続を毎回新しく作るのではなく、あらかじめ作っておいた接続を使い回します。
コネクションプーリングの流れ
| 順番 | 処理 |
|---|---|
| 1 | 事前にデータベースとの接続を複数作成してプールにためておく |
| 2 | Webアプリケーションがコネクションの取得を依頼する |
| 3 | 利用されていないConnectionを割り当てる |
| 4 | データベースを利用する |
| 5 | 使い終わったConnectionをプールに返す |
コネクションプーリングを使うと、リクエストごとに接続を新規作成する必要が少なくなります。
そのため、アクセス数が多いWebアプリケーションでは、負荷を抑えやすくなります。
コネクションプーリングを使う場面
この記事のサンプルでは、JDBCの基本を理解するためにDriverManager.getConnectionを使っています。
これは学習には分かりやすい方法です。
ただし、業務で使う本格的なWebアプリケーションでは、アプリケーションサーバが提供するコネクションプーリングを使うことがあります。
通常の接続とコネクションプーリングの違い
| 項目 | 通常の接続 | コネクションプーリング |
|---|---|---|
| 接続の作成 | リクエストごとに作成 | 事前に複数作成しておく |
| 接続の終了 | 処理後に切断 | プールへ返却 |
| 負荷 | アクセスが増えると大きくなりやすい | 接続作成の負荷を抑えやすい |
| 主な用途 | 学習、小規模な確認 | 業務向けの本格的なWebアプリケーション |
コネクションプーリングは、アプリケーションサーバが提供する機能です。
使い方はサーバによって異なるため、実際に利用する場合は、Tomcatなど使用しているサーバの設定方法を確認します。
データベース対応版で押さえたいこと
ひとこと広場をデータベース対応版にすると、投稿の保存先がアプリケーションスコープからHirobaデータベースのMESSAGESテーブルに変わります。
そのため、サーバを再起動しても投稿データを残せるようになります。
今回の構成では、データベースアクセスをMessagesDAOにまとめました。
Mainサーブレットは直接SQLを書かず、GetMessageListLogicやPostMessageLogicを呼び出します。
GetMessageListLogicはMessagesDAOのfindAllメソッドを使って投稿一覧を取得します。
PostMessageLogicはMessagesDAOのcreateメソッドを使って投稿を追加します。
ShortMessageクラスには、MESSAGESテーブルのID列に対応するidフィールドを追加しました。
main.jspは、Mainサーブレットがリクエストスコープに保存したmessageListを、EL式とJSTLで表示できます。
DAOを使って役割を分けることで、Servlet、Model、DAO、JSPの責任が整理されます。
データベース操作はDAOに任せる。
この考え方を身に付けると、ひとこと広場のようなWebアプリケーションを、より実践的で保守しやすい構成にできます。
