サーブレット&JSPの基本|みんなのショッピングのログイン機能を作成しよう

設計したログイン機能を、順番にプログラムへ落とし込む。Entity、DAO、BO、サーブレット、JSPをつなげて、みんなのショッピングのログイン処理を完成させよう

前の記事では、ショッピングサイト「みんなのショッピング」のログイン機能を設計しました。

ログイン機能を作るために、どのデータベースを使うのか、どのテーブルに会員情報を保存するのか、どの画面を用意するのか、どのサーブレットとJSPを使うのか、そしてサーバサイドでどのクラスを連携させるのかを整理しました。

今回の記事では、その設計をもとに、実際にプログラムを作成します。

プロジェクト名はstudyです。

データベース名はminnaMarketです。

テーブル名はCUSTOMERSです。

テスト用アカウントは、ユーザーIDをmiyu、パスワードを1234とします。

ログイン機能では、画面から入力されたユーザーIDとパスワードを受け取り、CUSTOMERSテーブルに登録されている会員情報と照合します。

一致する会員が見つかればログイン成功です。

ログイン成功時は、ユーザーIDをセッションスコープに保存し、ログイン成功画面を表示します。

一致する会員が見つからなければログイン失敗です。

ログイン失敗時は、ログイン画面へ戻します。

いきなりサーブレットやJSPから作り始めると、エラーが起きたときに原因を探しにくくなります。

そのため、この記事では次の順番で作成します。

作成の流れ

順番作業内容
1Entityクラスを作成するログイン情報と会員情報を表すクラスを作る
2DAOを作成するCUSTOMERSテーブルを検索するクラスを作る
3DAOをテストするデータベース検索が正しく動くか確認する
4BOを作成するログイン判定を行うクラスを作る
5BOをテストするログイン成功と失敗を確認する
6画面遷移を作成するサーブレットとJSPで画面の流れを先に作る
7画面遷移をテストするブラウザで画面の移動を確認する
8サーブレットを仕上げるリクエストパラメータ取得、BO呼び出し、スコープ処理を追加する
9JSPを仕上げるセッションスコープの値をEL式とJSTLで表示する
10機能を動作確認するログイン成功とログイン失敗をブラウザで確認する

この順番で作ると、どこまで正しく動いているかを段階的に確認できます。

特にDAOとBOは、一般的なJavaクラスとしてテストしやすいので、先に完成させておくと安心です。

まずモデルから作成する

ログイン機能を作るとき、最初にJSPを書きたくなるかもしれません。

画面が見えると作っている実感が出るためです。

しかし、ログイン機能ではデータベース検索とログイン判定が重要です。

この部分が正しく動いていないと、サーブレットやJSPを作っても、ログイン処理全体がうまく動きません。

そこで、まずはモデル側のクラスから作成します。

ここでいうモデル側のクラスとは、Entity、DAO、BOです。

モデル側で作成するクラス

種類クラス名パッケージ役割
EntityCustomerLoginmodel入力されたログイン情報を表す
EntityCustomerAccountmodelCUSTOMERSテーブルの1件分の会員情報を表す
DAOCustomersDAOdaoCUSTOMERSテーブルを検索する
BOCustomerLoginLogicmodelログイン認証の成否を判定する

Entityは、データを入れて運ぶためのクラスです。

DAOは、データベースアクセスを担当するクラスです。

BOは、アプリケーションの中心となる処理を担当するクラスです。

今回であれば、CustomerLoginLogicがログイン処理を担当します。

図1:モデルから作ってテストする流れ

この図から分かること

ログイン機能を作るときは、まずモデル側のクラスを作成します。

CustomerLoginは、ログイン画面から入力されたユーザーIDとパスワードを表します。

CustomerAccountは、CUSTOMERSテーブルの1件分の会員情報を表します。

CustomersDAOは、CUSTOMERSテーブルを検索します。

CustomerLoginLogicは、DAOの結果をもとにログイン成功か失敗かを判定します。

サーブレットやJSPを作る前にDAOとBOをテストしておくと、後からエラーが出たときに原因を絞り込みやすくなります。

ログイン情報を表すEntityを作成する

最初に、ログイン画面から入力された情報を表すEntityを作成します。

このクラスをCustomerLoginとします。

ログイン画面では、ユーザーIDとパスワードを入力します。

そのため、CustomerLoginクラスにはcustomerIdとloginPassの2つのフィールドを用意します。

CustomerLoginクラスのフィールド

フィールド内容
customerIdログイン画面で入力されたユーザーID
loginPassログイン画面で入力されたパスワード

CustomerLoginインスタンスは、CustomerLoginServletで作成されます。

その後、CustomerLoginLogicへ渡され、さらにCustomersDAOへ渡されます。

ログイン情報を表すEntityの例

ファイル名: CustomerLogin.java

package model;

public class CustomerLogin {
    private String customerId;
    private String loginPass;

    public CustomerLogin(String customerId, String loginPass) {
        this.customerId = customerId;
        this.loginPass = loginPass;
    }

    public String getCustomerId() {
        return customerId;
    }

    public String getLoginPass() {
        return loginPass;
    }
}

CustomerLogin.javaは、modelパッケージに作成します。

このクラスは、ログイン画面で入力されたユーザーIDとパスワードをまとめて扱うためのクラスです。

コンストラクタでは、customerIdとloginPassを受け取り、それぞれのフィールドに保存します。

getterメソッドを用意しているため、CustomersDAOではlogin.getCustomerIdやlogin.getLoginPassを使って値を取り出せます。

CUSTOMERSテーブルのレコードを表すEntityを作成する

次に、CUSTOMERSテーブルの1件分の会員情報を表すEntityを作成します。

このクラスをCustomerAccountとします。

CUSTOMERSテーブルには、CUSTOMER_ID、LOGIN_PASS、EMAIL、CUSTOMER_NAME、AGEの列があります。

Java側では、これらに対応するフィールドを用意します。

CustomerAccountクラスのフィールド

フィールド対応する列内容
customerIdCUSTOMER_IDユーザーID
loginPassLOGIN_PASSパスワード
emailEMAILメールアドレス
customerNameCUSTOMER_NAME氏名
ageAGE年齢

CustomersDAOで会員情報が見つかった場合、ResultSetから取り出した値を使ってCustomerAccountインスタンスを作成します。

CUSTOMERSテーブルのレコードを表すEntityの例

ファイル名: CustomerAccount.java

package model;

public class CustomerAccount {
    private String customerId;
    private String loginPass;
    private String email;
    private String customerName;
    private int age;

    public CustomerAccount(String customerId, String loginPass,
            String email, String customerName, int age) {
        this.customerId = customerId;
        this.loginPass = loginPass;
        this.email = email;
        this.customerName = customerName;
        this.age = age;
    }

    public String getCustomerId() {
        return customerId;
    }

    public String getLoginPass() {
        return loginPass;
    }

    public String getEmail() {
        return email;
    }

    public String getCustomerName() {
        return customerName;
    }

    public int getAge() {
        return age;
    }
}

CustomerAccount.javaは、modelパッケージに作成します。

このクラスは、CUSTOMERSテーブルの1件分の会員情報をJavaで扱うためのクラスです。

DAOがデータベースから会員情報を取得したとき、その結果をCustomerAccountインスタンスとして返します。

ログイン成功かどうかを判断するときは、CustomerAccountインスタンスが取得できたかどうかを確認します。

DAOを作成する

次に、CUSTOMERSテーブルを担当するDAOを作成します。

このクラスをCustomersDAOとします。

CustomersDAOには、findByLoginメソッドを用意します。

findByLoginメソッドは、CustomerLoginインスタンスを受け取り、CUSTOMERSテーブルから該当する会員を検索します。

findByLoginメソッドの役割

項目内容
引数CustomerLogin
処理CUSTOMER_IDとLOGIN_PASSが一致するレコードを検索する
戻り値見つかった場合はCustomerAccount、見つからない場合はnull
使用するSQLSELECT文
接続先minnaMarketデータベース

ログイン処理では、入力されたユーザーIDとパスワードの両方が一致するかどうかを確認します。

そのため、WHERE句ではCUSTOMER_IDとLOGIN_PASSの両方を条件にします。

CUSTOMERSテーブルを担当するDAOの例

ファイル名: CustomersDAO.java

package dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import model.CustomerAccount;
import model.CustomerLogin;

public class CustomersDAO {
    private final String JDBC_URL =
            "jdbc:h2:tcp://localhost/~/minnaMarket";
    private final String DB_USER = "sa";
    private final String DB_PASS = "";

    public CustomerAccount findByLogin(CustomerLogin login) {
        CustomerAccount account = null;

        // JDBCドライバを読み込む
        try {
            Class.forName("org.h2.Driver");
        } catch (ClassNotFoundException e) {
            throw new IllegalStateException(
                    "JDBCドライバを読み込めませんでした");
        }

        // データベースへ接続
        try (Connection conn = DriverManager.getConnection(
                JDBC_URL, DB_USER, DB_PASS)) {

            // SELECT文を準備
            String sql = "SELECT CUSTOMER_ID, LOGIN_PASS, EMAIL, "
                    + "CUSTOMER_NAME, AGE "
                    + "FROM CUSTOMERS "
                    + "WHERE CUSTOMER_ID = ? AND LOGIN_PASS = ?";
            PreparedStatement pStmt = conn.prepareStatement(sql);
            pStmt.setString(1, login.getCustomerId());
            pStmt.setString(2, login.getLoginPass());

            // SELECT文を実行し、結果表を取得
            ResultSet rs = pStmt.executeQuery();

            if (rs.next()) {
                // 会員が存在したらデータを取得し、
                // CustomerAccountインスタンスを作成する
                String customerId = rs.getString("CUSTOMER_ID");
                String loginPass = rs.getString("LOGIN_PASS");
                String email = rs.getString("EMAIL");
                String customerName = rs.getString("CUSTOMER_NAME");
                int age = rs.getInt("AGE");

                account = new CustomerAccount(customerId, loginPass,
                        email, customerName, age);
            }
        } catch (SQLException e) {
            e.printStackTrace();
            return null;
        }

        return account;
    }
}

CustomersDAO.javaは、daoパッケージに作成します。

JDBC_URLには、minnaMarketデータベースへ接続するためのJDBC URLを指定します。

findByLoginメソッドでは、CustomerLoginインスタンスからcustomerIdとloginPassを取り出し、PreparedStatementに設定しています。

SQL文の?には、pStmt.setStringで値を設定します。

1番目の?にはユーザーID、2番目の?にはパスワードを入れます。

該当する会員が見つかった場合は、CustomerAccountインスタンスを作成して返します。

見つからなかった場合は、accountの値はnullのままなので、nullが返ります。

DAOをテストする

DAOを作成したら、すぐに次のクラスへ進まず、DAOの動作を確認します。

findByLoginメソッドは、引数の値によって結果が変わります。

正しいユーザーIDとパスワードを渡した場合は、会員情報が取得できるはずです。

間違ったパスワードを渡した場合は、会員情報が取得できず、nullが返るはずです。

DAOテストで確認すること

テスト入力期待する結果
ログイン成功のテストcustomerIdがmiyu、loginPassが1234CustomerAccountが返る
ログイン失敗のテストcustomerIdがmiyu、loginPassが9999nullが返る

この2つを確認することで、CustomersDAOの基本的な動作を確認できます。

CustomersDAOをテストするクラスの例

ファイル名: CustomersDAOCheck.java

package test;

import dao.CustomersDAO;
import model.CustomerAccount;
import model.CustomerLogin;

public class CustomersDAOCheck {
    public static void main(String[] args) {
        testFindByLoginOK();
        testFindByLoginNG();
    }

    public static void testFindByLoginOK() {
        CustomerLogin login = new CustomerLogin("miyu", "1234");
        CustomersDAO dao = new CustomersDAO();
        CustomerAccount result = dao.findByLogin(login);

        if (result != null
                && result.getCustomerId().equals("miyu")
                && result.getLoginPass().equals("1234")
                && result.getEmail().equals("miyu.tanaka@example.jp")
                && result.getCustomerName().equals("田中 美優")
                && result.getAge() == 28) {
            System.out.println("testFindByLoginOK:成功しました");
        } else {
            System.out.println("testFindByLoginOK:失敗しました");
        }
    }

    public static void testFindByLoginNG() {
        CustomerLogin login = new CustomerLogin("miyu", "9999");
        CustomersDAO dao = new CustomersDAO();
        CustomerAccount result = dao.findByLogin(login);

        if (result == null) {
            System.out.println("testFindByLoginNG:成功しました");
        } else {
            System.out.println("testFindByLoginNG:失敗しました");
        }
    }
}

CustomersDAOCheck.javaは、testパッケージに作成します。

このクラスはmainメソッドを持っているため、Javaアプリケーションとして実行できます。

Eclipseでは、CustomersDAOCheck.javaを選択して右クリックし、実行、Javaアプリケーションの順に選択します。

実行結果が次のようになれば、DAOのテストは成功です。

testFindByLoginOK:成功しました
testFindByLoginNG:成功しました

テストに失敗した場合は、CUSTOMERSテーブルのデータ、CustomerLoginクラス、CustomerAccountクラス、CustomersDAOクラスを確認します。

特に、データベース名、テーブル名、列名、パスワードの値に誤りがないか確認しましょう。

単体テストとは

CustomersDAOCheckのように、特定のクラスやメソッドが正しく動くかを確認するテストを単体テストと呼びます。

今回のようにmainメソッドを使って簡単に確認する方法でも、学習段階では十分役に立ちます。

本格的な開発では、JUnitなどの単体テストフレームワークを使うことがあります。

単体テストの利点

利点内容
早い段階でミスに気付けるサーブレットと連携する前にDAOの問題を発見できる
原因を絞り込みやすいDAOだけを実行して確認できる
修正しやすいWeb画面を使わずにJavaクラスとして確認できる
安心して次へ進めるDAOが正しく動くことを確認してからBOを作れる

Webアプリケーション全体を一度に作ってから確認すると、エラーの原因がどこにあるのか分かりにくくなります。

こまめにテストしながら進めることが大切です。

BOを作成する

次に、ログイン処理を担当するBOを作成します。

このクラスをCustomerLoginLogicとします。

CustomerLoginLogicには、executeメソッドを用意します。

executeメソッドは、CustomerLoginインスタンスを受け取り、CustomersDAOのfindByLoginメソッドを呼び出します。

会員情報が取得できた場合はtrueを返します。

会員情報が取得できなかった場合はfalseを返します。

CustomerLoginLogicの役割

項目内容
クラス名CustomerLoginLogic
パッケージmodel
メソッドexecute
引数CustomerLogin
戻り値boolean
処理CustomersDAOを使ってログイン成功か失敗かを判定する

BOは、アプリケーションの中心となる処理を担当します。

今回であれば、ログインできるかどうかを判定するのがBOの役割です。

ログイン処理を担当するBOの例

ファイル名: CustomerLoginLogic.java

package model;

import dao.CustomersDAO;

public class CustomerLoginLogic {
    public boolean execute(CustomerLogin login) {
        CustomersDAO dao = new CustomersDAO();
        CustomerAccount account = dao.findByLogin(login);
        return account != null;
    }
}

CustomerLoginLogic.javaは、modelパッケージに作成します。

executeメソッドでは、CustomersDAOを作成し、findByLoginメソッドを呼び出します。

findByLoginメソッドの戻り値がnullでなければ、該当する会員が見つかったということです。

その場合はtrueを返します。

戻り値がnullであれば、該当する会員が見つからなかったということなので、falseを返します。

BOをテストする

BOを作成したら、DAOと同じようにテストします。

CustomerLoginLogicのexecuteメソッドは、ログイン成功時にtrue、ログイン失敗時にfalseを返す必要があります。

BOテストで確認すること

テスト入力期待する結果
ログイン成功のテストcustomerIdがmiyu、loginPassが1234true
ログイン失敗のテストcustomerIdがmiyu、loginPassが9999false

このテストに成功すれば、ログイン判定の中心部分が正しく動いていると確認できます。

CustomerLoginLogicをテストするクラスの例

ファイル名: CustomerLoginLogicCheck.java

package test;

import model.CustomerLogin;
import model.CustomerLoginLogic;

public class CustomerLoginLogicCheck {
    public static void main(String[] args) {
        testExecuteOK();
        testExecuteNG();
    }

    public static void testExecuteOK() {
        CustomerLogin login = new CustomerLogin("miyu", "1234");
        CustomerLoginLogic bo = new CustomerLoginLogic();
        boolean result = bo.execute(login);

        if (result) {
            System.out.println("testExecuteOK:成功しました");
        } else {
            System.out.println("testExecuteOK:失敗しました");
        }
    }

    public static void testExecuteNG() {
        CustomerLogin login = new CustomerLogin("miyu", "9999");
        CustomerLoginLogic bo = new CustomerLoginLogic();
        boolean result = bo.execute(login);

        if (!result) {
            System.out.println("testExecuteNG:成功しました");
        } else {
            System.out.println("testExecuteNG:失敗しました");
        }
    }
}

CustomerLoginLogicCheck.javaは、testパッケージに作成します。

このクラスもmainメソッドを持っているため、Javaアプリケーションとして実行できます。

実行結果が次のようになれば、BOのテストは成功です。

testExecuteOK:成功しました
testExecuteNG:成功しました

ここまでで、ログイン処理のモデル部分が完成しました。

CustomerLogin、CustomerAccount、CustomersDAO、CustomerLoginLogicが正しく動作することを確認できています。

この後は、サーブレットとJSPを作成し、ブラウザからログイン機能を使えるようにします。

図2:サーブレットとJSPで画面遷移を先に作る

この図から分かること

モデル部分を作成してテストしたら、次はサーブレットとJSPを作成します。

ただし、最初からログイン判定まで組み込むのではなく、まず画面遷移だけを作ります。

MarketTopServletからmarketTop.jspへフォワードし、ログインリンクからCustomerLoginServletへ移動します。

CustomerLoginServletのdoGetではcustomerLogin.jspを表示します。

最初の段階では、doPostはログイン成功画面へフォワードするだけにします。

画面遷移を先に確認しておくと、この後の仕上げ作業が進めやすくなります。

トップ画面を表示するサーブレットを作成する

ここからは、サーブレットとJSPを作成します。

まず、トップ画面を表示するMarketTopServletを作ります。

この段階では、特別な処理は行いません。

marketTop.jspへフォワードするだけです。

トップ画面に関するリクエストを処理するコントローラの例

ファイル名: MarketTopServlet.java

package servlet;

import java.io.IOException;

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;

@WebServlet("/MarketTopServlet")
public class MarketTopServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        RequestDispatcher dispatcher = request.getRequestDispatcher(
                "WEB-INF/jsp/marketTop.jsp");
        dispatcher.forward(request, response);
    }
}

MarketTopServlet.javaは、servletパッケージに作成します。

@WebServlet("/MarketTopServlet")により、ブラウザからMarketTopServletへアクセスできるようになります。

doGetでは、RequestDispatcherを使ってWEB-INF/jsp/marketTop.jspへフォワードしています。

JSPをWEB-INFの中に置くことで、JSPへ直接アクセスするのではなく、サーブレットを通して表示する流れにできます。

トップ画面を作成する

次に、トップ画面を表示するmarketTop.jspを作成します。

この画面には、ログイン画面へ移動するリンクと、ユーザー登録の項目を表示します。

ユーザー登録機能はまだ作成しないため、ここでは文字だけを表示します。

トップ画面を出力するビューの例

ファイル名: marketTop.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>みんなのショッピング</title>
</head>
<body>
<h1>みんなのショッピング</h1>
<ul>
<li><a href="CustomerLoginServlet">ログイン</a></li>
<li>ユーザー登録</li>
</ul>
</body>
</html>

marketTop.jspは、src/main/webapp/WEB-INF/jspに配置します。

ログインリンクのhrefにはCustomerLoginServletを指定しています。

これにより、ログインをクリックするとCustomerLoginServletのdoGetが実行されます。

ログイン画面とログイン成功画面への遷移を作る

次に、ログイン画面を表示するCustomerLoginServletを作成します。

最初の段階では、ログイン処理はまだ組み込みません。

doGetではcustomerLogin.jspへフォワードします。

doPostでは、ログイン成功画面であるcustomerLoginOK.jspへフォワードします。

このように、まず画面遷移だけを完成させます。

ログインに関するリクエストを処理するコントローラの例

ファイル名: CustomerLoginServlet.java

package servlet;

import java.io.IOException;

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;

@WebServlet("/CustomerLoginServlet")
public class CustomerLoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        RequestDispatcher dispatcher = request.getRequestDispatcher(
                "WEB-INF/jsp/customerLogin.jsp");
        dispatcher.forward(request, response);
    }

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        RequestDispatcher dispatcher = request.getRequestDispatcher(
                "WEB-INF/jsp/customerLoginOK.jsp");
        dispatcher.forward(request, response);
    }
}

この段階のCustomerLoginServletでは、ログイン判定をしていません。

doPostが実行されると、常にcustomerLoginOK.jspへフォワードします。

これは、画面遷移を先に完成させるための仮の実装です。

後で、リクエストパラメータを取得し、CustomerLoginLogicを呼び出す処理を追加します。

ログイン画面を作成する

次に、ログイン画面を作成します。

ログイン画面では、ユーザーIDとパスワードを入力します。

入力された値は、POSTメソッドでCustomerLoginServletへ送信します。

ログイン画面を出力するビューの例

ファイル名: customerLogin.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>みんなのショッピング</title>
</head>
<body>
<h1>ログイン</h1>
<form action="CustomerLoginServlet" method="post">
ユーザーID:<input type="text" name="customerId"><br>
パスワード:<input type="password" name="loginPass"><br>
<input type="submit" value="ログイン">
</form>
</body>
</html>

customerLogin.jspは、src/main/webapp/WEB-INF/jspに配置します。

formタグのactionにはCustomerLoginServletを指定します。

methodにはpostを指定します。

ユーザーIDのinputタグにはname属性としてcustomerIdを指定します。

パスワードのinputタグにはname属性としてloginPassを指定します。

このname属性の値は、後でCustomerLoginServletのrequest.getParameterで取得する名前と一致させます。

ログイン成功画面を仮作成する

次に、ログイン成功画面を作成します。

この段階では、まだセッションスコープにユーザーIDを保存していません。

そのため、ユーザーIDの部分はダミー表示にしておきます。

ログイン成功画面を出力するビューの例

ファイル名: customerLoginOK.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>みんなのショッピング</title>
</head>
<body>
<h1>ログイン成功</h1>
<p>ようこそ[ユーザーID]さん</p>
<a href="MarketTopServlet">トップへ</a>
</body>
</html>

customerLoginOK.jspは、src/main/webapp/WEB-INF/jspに配置します。

この段階では、[ユーザーID]をダミーとして表示しています。

後でサーブレットを仕上げたあと、セッションスコープに保存されたcustomerIdをJSTLのc:outで表示するように変更します。

画面遷移をテストする

ここまでで、トップ画面、ログイン画面、ログイン成功画面の画面遷移を確認できます。

ブラウザから次のURLへアクセスします。

http://localhost:8080/study/MarketTopServlet

ブラウザの表示例

画面遷移の確認手順

手順操作確認すること
1MarketTopServletへアクセスするトップ画面が表示される
2ログインをクリックするログイン画面が表示される
3任意のユーザーIDとパスワードを入力する入力欄に値を入力できる
4ログインボタンをクリックするログイン成功画面が表示される
5トップへをクリックするトップ画面へ戻る

この段階では、正しいユーザーIDやパスワードを入力しなくてもログイン成功画面へ進みます。

まだログイン判定を組み込んでいないためです。

ここで確認したいのは、あくまで画面遷移が正しく行われるかどうかです。

図3:仕上げ後のログイン処理の全体像

この図から分かること

仕上げ後のログイン処理では、customerLogin.jspからCustomerLoginServletへPOSTリクエストが送信されます。

CustomerLoginServletは、customerIdとloginPassを取得し、CustomerLoginインスタンスを作成します。

CustomerLoginLogicは、CustomersDAOを使ってCUSTOMERSテーブルを検索します。

ログインに成功した場合は、customerIdをセッションスコープに保存し、customerLoginOK.jspへフォワードします。

ログインに失敗した場合は、CustomerLoginServletへリダイレクトし、ログイン画面を再表示します。

サーブレットを仕上げる

画面遷移が確認できたら、CustomerLoginServletを仕上げます。

ここでは、doPostに次の処理を追加します。

doPostに追加する処理

順番処理
1文字コードをUTF-8に設定する
2リクエストパラメータcustomerIdを取得する
3リクエストパラメータloginPassを取得する
4CustomerLoginインスタンスを作成する
5CustomerLoginLogicのexecuteメソッドを呼び出す
6ログイン成功時はセッションスコープにcustomerIdを保存する
7ログイン成功時はcustomerLoginOK.jspへフォワードする
8ログイン失敗時はCustomerLoginServletへリダイレクトする

ログイン成功時と失敗時で処理を分岐させます。

成功時はセッションスコープを使います。

失敗時はセッションスコープに何も保存せず、ログイン画面へ戻します。

ログインに関するリクエストを処理するコントローラの例

ファイル名: CustomerLoginServlet.java

package servlet;

import java.io.IOException;

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.CustomerLogin;
import model.CustomerLoginLogic;

@WebServlet("/CustomerLoginServlet")
public class CustomerLoginServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    protected void doGet(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        RequestDispatcher dispatcher = request.getRequestDispatcher(
                "WEB-INF/jsp/customerLogin.jsp");
        dispatcher.forward(request, response);
    }

    protected void doPost(HttpServletRequest request,
            HttpServletResponse response)
            throws ServletException, IOException {

        // リクエストパラメータの取得
        request.setCharacterEncoding("UTF-8");
        String customerId = request.getParameter("customerId");
        String loginPass = request.getParameter("loginPass");

        // ログイン処理の実行
        CustomerLogin login = new CustomerLogin(customerId, loginPass);
        CustomerLoginLogic bo = new CustomerLoginLogic();
        boolean result = bo.execute(login);

        // ログイン処理の成否によって処理を分岐
        if (result) {
            // セッションスコープにユーザーIDを保存
            HttpSession session = request.getSession();
            session.setAttribute("customerId", customerId);

            // ログイン成功画面へフォワード
            RequestDispatcher dispatcher = request.getRequestDispatcher(
                    "WEB-INF/jsp/customerLoginOK.jsp");
            dispatcher.forward(request, response);
        } else {
            // ログイン画面へリダイレクト
            response.sendRedirect("CustomerLoginServlet");
            return;
        }
    }
}

このCustomerLoginServlet.javaが、ログイン処理を組み込んだ完成版です。

doGetは、ログイン画面を表示するために使います。

doPostは、ログインフォームから送信された値を受け取り、ログイン処理を行います。

request.setCharacterEncoding("UTF-8")は、POSTで送信された日本語などを正しく扱うために指定しています。

request.getParameter("customerId")でユーザーIDを取得します。

request.getParameter("loginPass")でパスワードを取得します。

その後、CustomerLoginインスタンスを作成し、CustomerLoginLogicのexecuteメソッドに渡します。

executeメソッドの戻り値がtrueであればログイン成功です。

ログイン成功時は、HttpSessionを取得し、customerIdという属性名でセッションスコープにユーザーIDを保存します。

その後、customerLoginOK.jspへフォワードします。

executeメソッドの戻り値がfalseであればログイン失敗です。

ログイン失敗時は、CustomerLoginServletへリダイレクトします。

リダイレクト後はdoGetが実行され、customerLogin.jspが再表示されます。

JSPファイルを仕上げる

最後に、ログイン成功画面のJSPを仕上げます。

仮作成したcustomerLoginOK.jspでは、[ユーザーID]というダミー文字列を表示していました。

完成版では、セッションスコープに保存されたcustomerIdを表示します。

ここでは、スクリプトレットやスクリプト式を使わず、EL式とJSTLのc:outを使います。

仕上げで変更する部分

変更前変更後
ようこそ[ユーザーID]さんようこそ<c:out value="${customerId}" />さん

c:outを使うと、スコープに保存された値を安全に表示できます。

ログイン成功時にCustomerLoginServletがセッションスコープへcustomerIdを保存しているため、JSP側では${customerId}で取り出せます。

ログイン成功画面を出力するビューの例

ファイル名: customerLoginOK.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="jakarta.tags.core" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>みんなのショッピング</title>
</head>
<body>
<h1>ログイン成功</h1>
<p>ようこそ<c:out value="${customerId}" />さん</p>
<a href="MarketTopServlet">トップへ</a>
</body>
</html>

customerLoginOK.jspは、src/main/webapp/WEB-INF/jspに配置します。

JSTLを使うために、taglibディレクティブを記述しています。

prefixにはcを指定し、uriにはjakarta.tags.coreを指定します。

c:outのvalue属性には${customerId}を指定します。

これにより、セッションスコープに保存されたcustomerIdが画面に表示されます。

ログイン成功後、ユーザーIDがmiyuであれば、画面には次のように表示されます。

ようこそmiyuさん

機能を動作確認する

すべてのプログラムとJSPを作成したら、ブラウザでログイン機能を確認します。

まず、MarketTopServletへアクセスします。

http://localhost:8080/study/MarketTopServlet

トップ画面が表示されたら、ログインをクリックします。

ログイン画面で、次の値を入力します。

ログイン成功の確認

入力項目入力値
ユーザーIDmiyu
パスワード1234

ログインボタンをクリックし、ログイン成功画面にようこそmiyuさんと表示されれば成功です。

次に、パスワードを間違えてログイン失敗を確認します。

ログイン失敗の確認

入力項目入力値
ユーザーIDmiyu
パスワード9999

この場合は、ログイン成功画面へ進まず、ログイン画面へ戻れば正常です。

動作確認で見るポイント

ログイン機能の動作確認では、画面が表示されるかだけでなく、処理の流れも確認します。

確認ポイント

確認項目確認する内容
トップ画面MarketTopServletからmarketTop.jspが表示されるか
ログイン画面ログインリンクからcustomerLogin.jspへ移動できるか
POST送信ログインボタンでCustomerLoginServletのdoPostが実行されるか
成功時miyu、1234でログイン成功画面へ進むか
セッションスコープcustomerIdが保存され、JSPで表示されるか
失敗時間違ったパスワードでログイン画面へ戻るか
DAOCUSTOMERSテーブルを正しく検索できているか
BOtrueとfalseを正しく返しているか

エラーが出た場合は、どこまで動いているかを順番に確認します。

DAOとBOはすでにテスト済みなので、サーブレットやJSPを中心に確認できます。

たとえば、ログイン成功画面にユーザーIDが表示されない場合は、CustomerLoginServletでsession.setAttribute("customerId", customerId)が実行されているか、customerLoginOK.jspで${customerId}を指定しているかを確認します。

次の機能へ進むときの考え方

ログイン機能が完成したら、次の機能へ進めます。

たとえば、ユーザー登録機能を追加する場合は、今回作成した画面遷移図を拡張すると分かりやすくなります。

トップ画面のユーザー登録から、ユーザー登録画面へ進み、登録完了画面へ進む流れを追加します。

次に拡張できる機能

機能内容
ユーザー登録機能新しい会員をCUSTOMERSテーブルに追加する
ログアウト機能セッションスコープを破棄してトップ画面へ戻る
商品一覧機能商品テーブルから商品情報を取得して表示する
マイページ機能ログイン中の会員情報を表示する

1つの機能が完成したら、次の機能も同じように設計から始めます。

画面、サーブレット、JSP、Entity、BO、DAO、テーブルを整理し、少しずつ追加していきます。

ログイン機能で身に付けたいこと

今回作成したログイン機能では、Webアプリケーション開発でよく使う流れをまとめて確認しました。

今回扱った主な内容

内容学習したこと
Entity入力情報やテーブルの1件分をJavaクラスで表す
DAOデータベース検索を専用クラスにまとめる
BOアプリケーションの中心処理を担当する
単体テストDAOやBOをサーブレットと連携する前に確認する
サーブレットリクエストを受け取り、処理の流れを制御する
JSP画面を表示する
セッションスコープログイン中のユーザーIDを保存する
EL式とJSTLJSPでスコープの値を安全に表示する

ログイン機能は、Webアプリケーションの入口になる大切な機能です。

画面だけでなく、データベース、DAO、BO、サーブレット、JSPが連携して動きます。

今回のように、モデルから作成してテストし、その後で画面遷移を作り、最後にサーブレットとJSPを仕上げる流れを身に付けると、ほかの機能も落ち着いて作りやすくなります。

まずは、みんなのショッピングのログイン機能を完成させ、次の機能へ少しずつ広げていきましょう。