【サーブレット/JSP/JDBC】会員登録機能の実装を覚える

前回サーブレット/JSPで制作したフォームに、JDBCを使って会員登録機能を実装するやり方を覚えるための自分用メモです。

# 前回の記事
https://qiita.com/miya71/items/100abae2f6899737d567

# 使用環境
PC：MacBook Air 2018
OS：macOS Sonoma 14.3.1
IDE：Eclipse 2023
Webコンテナ：Tomcat 10.1.18
RDBMS：MySQL 8.3.0

# JDBCとは？
JDBC（Java Datebase Connectivity）とは、JavaからRDB（リレーショナルデータベース）に接続するためのJava API（java.sqlパッケージ）です。
JDBCを使うには、接続するRDBに対応した各ドライバを用意する必要があります。

## MySQL用のJDBCドライバ（MySQL Connector/J）をインストールする
JavaでMySQLに接続する場合は、MySQLコネクタの中からMySQL用のJDBCドライバであるMySQL Connector/Jを探してインストールします。
https://www.mysql.com/jp/products/connector/

## JarファイルをTomCatのlibディレクトリに配置する
インストールしたフォルダの中のJarファイルを、TomCatのlibディレクトリ（PATHは自分の環境だと/Applications/Eclipse_2023-12.app/Contents/tomcat/10/lib）に配置します。

これで、JDBCを使ったWebアプリケーションを開発する準備ができました。

# DAOパターンの準備
DAOパターンとは、メインロジックとは別にデータベースにアクセスするクラス（DAO、Data Access Object）を作成し、メインロジックはそのDAOを通してデータベースにアクセスするというデザインパターンです。

ここからは、DAOパターンを使ってJDBCによるデータベースのアクセスと、会員登録機能の実装を行います。
## MySQLでテーブルを用意する
JDBCを使う前に、MySQLで会員登録用のテーブルを用意します。
今回は、以下のコマンドで用意しました。
```
mysql> create table members (
    -> id int auto_increment not null primary key,
    -> name varchar(40) not null,
    -> gender varchar(10) not null,
    -> year int not null,
    -> month int not null,
    -> day int not null,
    -> email varchar(100) not null,
    -> password varchar(10) not null
    -> );
```

## DAOを用意する
テーブルを用意したら、そのテーブルに対応するDAO（今回はinsertメソッドのみ）を用意します。
```java:MemberDao.java
package dao;

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

import bean.MemberBean;

public class MemberDao {
    private Connection con = null;
	private final String url = "jdbc:mysql://localhost/mydb";
	private final String user = "root";
	private final String pass = ""; // 自分のrootパスワードを入力する

    public boolean insert(MemberBean memberBean) {
        try {
            // MySQLに接続する
			con = DriverManager.getConnection(url, user, pass);

    		// SQL文の雛形を準備する
    		String sql = "insert into members(name, gender, year, month, day, email, password) values(?, ?, ?, ?, ?, ?, ?)";
    		PreparedStatement pstmt = con.prepareStatement(sql);
		
    		// SQL文に値をセットして実行する
    		pstmt.setString(1, memberBean.getName());
    		pstmt.setString(2, memberBean.getGender());
    		pstmt.setString(3, memberBean.getYear());
    		pstmt.setString(4, memberBean.getMonth());
    		pstmt.setString(5, memberBean.getDay());
    		pstmt.setString(6, memberBean.getEmail());
    		pstmt.setString(7, memberBean.getPassword());
    		int result = pstmt.executeUpdate();

            // 処理結果が1以外ならfalseを返す
    		if(result != 1) {
    			return false;
            }
            pstmt.close();
    	} catch(SQLException e) {
            e.printStackTrace();
            return false;
        } finally {
            // MySQLの接続を切る
            try {
				if(con != null) {
					con.close();
				}
			} catch(SQLException e) {
				e.printStackTrace();
			}
        }
        return true;
    }
}
```

## DAOをインスタンス化するビジネスロジック層のクラスを作成する
3層アーキテクチャで言うと、DAOはデータアクセス層でサーブレット/JSPはプレゼンテーション層なので、その橋渡しとなるビジネスロジック層のクラスでDAOをインスタンス化してロジックを記述します。
```java:RegistrationLogic.java
package model;

import bean.MemberBean;
import dao.MemberDao;

public class RegistrationLogic {
	public boolean excute(MemberBean memberBean) {
		MemberDao memberDao = new MemberDao();
		return memberDao.insert(memberBean);
	}
}
```

# 会員登録用のサーブレットを作成する
DAO、Logicクラスを作成したら、入力確認画面のJSPファイルに会員登録用のサーブレットとなる、RegistrationServletへのリンクを記述します。
```jsp:confirmRegistrationForm.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>
<%@ page import="bean.MemberBean" %>
<%
MemberBean memberBean = (MemberBean)session.getAttribute("memberBean");
%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>入力内容確認</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header id="header">
        <div class="flex-item wrapper">
            <h1><a href="IndexServlet">SampleSite</a></h1>
        </div>
    </header>
    <main>
        <section id="confirm-registration">
            <h2>入力内容確認</h2>
            <p>入力内容を確認の上、登録ボタンをクリックしてください。</p>
            <div class="row">
                <h3>名前</h3>
                <p><%= memberBean.getName() %></p>
            </div>
            <div class="row">
                <h3>性別</h3>
                <p><%= memberBean.getGender() %></p>
            </div>
            <div class="row">
                <h3>生年月日</h3>
                <p><%= memberBean.getYear() %>年<%= memberBean.getMonth() %>月<%= memberBean.getDay() %>日</p>
            </div>
            <div class="row">
                <h3>メールアドレス</h3>
                <p><%= memberBean.getEmail() %></p>
            </div>
            <div class="row">
                <h3>パスワード</h3>
                <p><%= memberBean.getPassword() %></p>
            </div>
            <div class="flex-button">
                <div class="white-button"><a href="RegistrationFormServlet">戻る</a></div>
                <div class="black-button"><a href="RegistrationServlet">登録</a></div>
            </div>
        </section>
    </main>
</body>
</html>
```
会員登録用のサーブレットでは、入力内容の入ったEntityのインスタンスを登録ロジックのクラスに渡して処理結果を受け取り、結果に応じて登録完了画面、または登録失敗画面を表示します。
```java:RegistrationServlet.java
package servlet;

import java.io.IOException;

import bean.MemberBean;
import jakarta.servlet.RequestDispatcher;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import model.RegistrationLogic;

public class RegistrationServlet extends HttpServlet {
    @Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        // セッションスコープからBeanのインスタンスを受け取る
		HttpSession session = request.getSession();
		MemberBean memberBean = (MemberBean)session.getAttribute("memberBean");

        // LogicにBeanのインスタンスを渡して処理結果を受け取る
		RegistrationLogic registrationLogic = new RegistrationLogic();
		if(registrationLogic.excute(memberBean)) {
			// JSPファイルを指定してフォワード
            RequestDispatcher dispatcher = request.getRequestDispatcher("WEB-INF/jsp/completedRegistration.jsp");
            dispatcher.forward(request, response);
		} else {
            // JSPファイルを指定してフォワード
            RequestDispatcher dispatcher = request.getRequestDispatcher("WEB-INF/jsp/registrationError.jsp");
            dispatcher.forward(request, response);
        }
	}
}
```
登録完了画面、登録失敗画面のJSPファイルも必要なので、別途作成します。
```jsp:completedRegistration.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>会員登録完了</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header id="header">
        <div class="flex-item wrapper">
            <h1><a href="IndexServlet">SampleSite</a></h1>
        </div>
    </header>
    <main>
        <section id="completed-registration">
            <h2>会員登録完了</h2>
            <p>会員登録が完了しました。</p>
            <div class="black-button"><a href="IndexServlet">トップに戻る</a></div>
        </section>
    </main>
</body>
</html>
```
```jsp:registrationError.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>会員登録エラー</title>
    <link rel="stylesheet" href="css/style.css">
</head>
<body>
    <header id="header">
        <div class="flex-item wrapper">
            <h1><a href="IndexServlet">SampleSite</a></h1>
        </div>
    </header>
    <main>
        <section id="registration-error">
            <h2>会員登録エラー</h2>
            <p>会員登録に失敗しました。</p>
            <div class="black-button"><a href="IndexServlet">トップに戻る</a></div>
        </section>
    </main>
</body>
</html>
```

# Eclipseから実行してみる
登録完了後、ターミナルで以下のコマンドを入力してテーブルに入力内容が反映されていればOKです。
```
mysql> select * from members;
```