サーブレット&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側の準備

順番作業内容
1Hirobaデータベースを作成するひとこと広場用のデータベースを用意する
2MESSAGESテーブルを作成する投稿を保存するテーブルを用意する
3初期レコードを追加する動作確認用の投稿データを登録する
4JDBCドライバを配置するHirobaプロジェクトのWEB-INF/libにH2のJARを配置する

Hirobaプロジェクト側の準備

項目内容
プロジェクト名Hiroba
データベース名Hiroba
JDBCドライバ配置先src/main/webapp/WEB-INF/lib
使用するJSPEL式と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テーブル

IDUSER_NAMETEXT
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データベース作成の手順

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

データベースの作成が確認できたら、左上の切断ボタンで接続を解除します。

このあとのテーブル作成やデータ追加では、サーバーモードで接続します。

サーバーモードでHirobaデータベースに接続する

Hirobaデータベースを作成したら、次にサーバーモードで接続します。

H2コンソールの保存済み設定でGeneric H2 (Server)を選択します。

JDBC URLには、次の値を指定します。

jdbc:h2:tcp://localhost/~/Hiroba

サーバーモード接続の設定

項目設定値
保存済み設定Generic H2 (Server)
JDBC URLjdbc: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 MESSAGESMESSAGESテーブルを作成する
ID INT PRIMARY KEY AUTO_INCREMENTID列を主キーにし、自動連番にする
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件の投稿データが追加されます。

追加されるデータ

IDUSER_NAMETEXT
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文を実行します。

ひとことリスト取得の流れ

ひとこと一覧を表示するときは、データベースから投稿一覧を取得します。

ひとことリスト取得の手順

順番処理
1MainサーブレットがGetMessageListLogicのexecuteメソッドを呼び出す
2GetMessageListLogicがMessagesDAOのfindAllメソッドを呼び出す
3MessagesDAOがMESSAGESテーブルにSELECT文を実行する
4MessagesDAOがResultSetからShortMessageインスタンスを作成する
5ShortMessageインスタンスをListに追加する
6ListをGetMessageListLogicへ返す
7GetMessageListLogicがListをMainサーブレットへ返す
8MainサーブレットがmessageListとしてリクエストスコープに保存する
9main.jspがEL式とJSTLで一覧表示する

この流れにより、main.jspはデータベースを直接意識せず、これまでと同じようにmessageListを表示できます。

ひとこと投稿の流れ

投稿フォームからひとことが送信された場合は、入力内容をデータベースへ追加します。

ひとこと投稿の手順

順番処理
1MainサーブレットがPOSTリクエストを受け取る
2リクエストパラメータtextを取得する
3セッションスコープからloginUserを取得する
4loginUserの名前と入力内容からShortMessageを作成する
5MainサーブレットがPostMessageLogicのexecuteメソッドを呼び出す
6PostMessageLogicがMessagesDAOのcreateメソッドを呼び出す
7MessagesDAOがMESSAGESテーブルにINSERT文を実行する
8投稿後の一覧を再取得する
9main.jspへフォワードする

これまでのように、Listにaddしてメモリ上に投稿を追加するのではありません。

データベース対応版では、INSERT文でMESSAGESテーブルに投稿を追加します。

ShortMessageクラスをデータベース対応に変更する

まず、ひとことの情報を表すShortMessageクラスを変更します。

これまでのShortMessageクラスは、userNameとtextを持っていました。

データベース対応版では、MESSAGESテーブルのID列にも対応できるように、idフィールドを追加します。

ShortMessageクラスのフィールド

フィールド対応する列内容
idID投稿ID
userNameUSER_NAME投稿者名
textTEXT投稿内容

新しく投稿するときは、まだ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の役割

メソッド内容
findAllMESSAGESテーブルの全レコードを取得する
createMESSAGESテーブルに新しいレコードを追加する

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メソッドの流れ

順番処理内容
1Listを作成するShortMessageを入れるmessageListを用意する
2JDBCドライバを読み込むorg.h2.Driverを読み込む
3データベースへ接続するHirobaデータベースへ接続する
4SELECT文を準備するMESSAGESテーブルから全投稿を取得する
5SELECT文を実行するResultSetを取得する
6ResultSetを1行ずつ読むrs.nextで次のレコードへ進む
7列の値を取得するID、USER_NAME、TEXTを取り出す
8ShortMessageを作成する取得した値をインスタンスに入れる
9Listへ追加するmessageListにShortMessageを追加する
10Listを返す呼び出し元へ投稿一覧を返す

この処理により、MESSAGESテーブルの投稿データをJavaのListとして扱えるようになります。

MainサーブレットやJSPは、ResultSetを直接扱う必要がありません。

MessagesDAOのcreateメソッドの処理

createメソッドは、新しい投稿をデータベースへ追加する処理です。

createメソッドの流れ

順番処理内容
1JDBCドライバを読み込むorg.h2.Driverを読み込む
2データベースへ接続するHirobaデータベースへ接続する
3INSERT文を準備するUSER_NAMEとTEXTを追加するSQLを用意する
4?に値を設定する投稿者名と投稿内容を設定する
5INSERT文を実行する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の変更点

項目これまでデータベース対応後
保存先アプリケーションスコープのmessageListMESSAGESテーブル
引数messageListとShortMessageShortMessageのみ
追加方法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
役割投稿一覧を取得する
呼び出すDAOMessagesDAO
呼び出すメソッド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.getAttributeGetMessageListLogicを呼び出す
投稿の追加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文を実行します。

保存処理の違い

項目これまでデータベース対応後
投稿先messageListMESSAGESテーブル
保存場所アプリケーションスコープH2 DatabaseのHirobaデータベース
追加処理Listへ追加INSERT文
取得処理application.getAttributeSELECT文
サーバ再起動後投稿が消える投稿が残る

この変更により、ひとこと広場はサーバを再起動しても投稿が残るWebアプリケーションになります。

DAO化したときの役割分担

DAOを使うと、クラスごとの役割が整理されます。

クラスごとの役割

クラス役割
Mainリクエストを受け取り、ログイン確認、モデル呼び出し、フォワードを行う
GetMessageListLogic投稿一覧を取得する処理を担当する
PostMessageLogic投稿を保存する処理を担当する
MessagesDAOMESSAGESテーブルへのSELECT文とINSERT文を担当する
ShortMessageMESSAGESテーブルの1件分のデータを表す
main.jspEL式と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事前にデータベースとの接続を複数作成してプールにためておく
2Webアプリケーションがコネクションの取得を依頼する
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アプリケーションを、より実践的で保守しやすい構成にできます。