【JSP/Servlet】簡単なチャット機能の作り方

## はじめに
初学者向けのJavaサーブレットを用いた、もっとも原始的なチャット機能の実装方法です。
個人での開発やスクールでの課題作成時に参考にしてください。

## 開発環境

| カテゴリー | ソフトウェア名	 | バージョン |
|:-:|:-:|:-:|
| OS |  Windows10 | 21H2  |
| エディタ | Eclipse 2018 |  4.8.0 |
| データベース |  MySQL |  5.5.39 |

## 設計

**ER図**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1270216/f4306667-3509-22e6-0f4c-c3fb6a043865.png)


**DB設計書**
* ユーザ管理テーブル
* メッセージ管理テーブル
* チャットルーム管理テーブル

データベースは、以上の3つに分かれています。

① ユーザ管理テーブル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1270216/c74710bc-4a83-c62a-0b84-f7cdc0429abb.png)

```sql
CREATE TABLE accountinfo(
account_id VARCHAR(50) PRIMARY KEY NOT NULL,
password VARCHAR(20) NOT NULL
)ENGINE=InnoDB;
```

② メッセージ管理テーブル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1270216/aab2be39-b533-5171-b7ae-8365dcda64da.png)

```sql
CREATE TABLE messageinfo(
message_id INTEGER PRIMARY KEY NOT NULL AUTO_INCREMENT,
room_id INTEGER,
account_id VARCHAR(50),
message VARCHAR(300),
creation_day TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY(room_id) REFERENCES roominfo(room_id) on update cascade on delete cascade,
FOREIGN KEY(account_id) REFERENCES accountinfo(account_id) on update cascade on delete cascade
)ENGINE=InnoDB;
```

③ チャットルーム管理テーブル
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1270216/b832e358-dc59-6bcb-f7c6-4230b3344b7c.png)



```sql
CREATE TABLE roominfo(
room_id INTEGER AUTO_INCREMENT PRIMARY KEY NOT NULL,
account_id VARCHAR(50),
FOREIGN KEY(account_id) REFERENCES accountinfo(account_id) ON UPDATE CASCADE ON DELETE CASCADE
)ENGINE=InnoDB;
```

## JSP
仕組みはとても単純で
[iframe](https://e-words.jp/w/iframe.html)を用いて、チャットを表示している画面を埋め込むというものです。

* messageroom.jsp（メイン画面 ）
* message.jsp（チャットのみ表示）

**messageroom.jsp**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1270216/bc69df96-d64b-c6b5-e16a-4df9a84a2e27.png)

```jsp
<%@page contentType="text/html;charset=UTF-8"%>
<!-- ルームIDをリクエストスコープから取得 -->
<%String roomid =  (String) request.getAttribute("roomid");%>

<html>
<head>
<title>チャット</title>
<link rel="stylesheet" type="text/css" href="<%=request.getContextPath()%>/css/stylesheet.css">
</head>
<body>
	<div class="item-wrapper">
		<div class="container">
			<!-- チャット画面を埋め込む -->
			<iframe src="<%= request.getContextPath()%>/message?roomid=<%=roomid%>" width="650px" height="500px margin:0 auto;"></iframe>
			<!-- メッセージ送信用の入力欄 -->
			<form method="post" action="<%=request.getContextPath()%>/message?roomid=<%=roomid%>&cmd=1">
			<p>
				<input type=text size="70" name="message" placeholder="メッセージを入力してください"></input>
				<input type="submit" value="送信"></input>
			</p>
			</form>
		</div>
	</div>
</body>
</html>
```

**message.jsp**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1270216/498e8750-0017-2165-5d35-8f428275d130.png)


```jsp
<%@page contentType="text/html;charset=UTF-8"%>
<%@page import="java.util.ArrayList,bean.User,bean.Message"%>

<%
//メッセージリストを読み込み
ArrayList<Message> messageList = (ArrayList<Message>)request.getAttribute("messageList");
User userinfo = (User)request.getAttribute("userinfo");
%>

<html>
<head>
<meta charset="utf-8">
<title>メッセージ</title>
</head>
<body style="background-color:#ffffff">
<div style="width:100%;">
<%if (messageList.size() != 0) {
	for (Message message:messageList) { %>
	<% if (message.getSenderId().equals(userinfo.getUser_id())) { %>
		<div style="margin-left: 0px; text-align: right;">
			<p>【<%=userinfo.getUser_name() %>】</p>
			<p><%=message.getMessage() %></p>
		</div>
	<%} else { %>
		<div style="margin-right: 0px;">
			<p>【<%=message.getSenderId() %>】</p>
			<p><%=message.getMessage() %></p>
		</div>
<%
	  }
	}
}
%>
</div>
</body>
</html>
```
## Servlet
**messageServlet**
```java
package servlet;

import java.io.IOException;
import java.util.ArrayList;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import bean.Message;
import bean.User;
import dao.MessageDAO;
import dao.UserDAO;

public class MessageServlet extends HttpServlet{

	//インスタンス化
	MessageDAO messageDao = new MessageDAO();
	UserDAO userDao = new UserDAO();

	String error = "";
	String cmd = "";
    
    //メッセージ表示用
	private static final long serialVersionUID = 1L;
	public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		// 文字エンコーディングの指定
		request.setCharacterEncoding("UTF-8");

		//パラメーターからデータ取得
		int roomid = Integer.parseInt(request.getParameter("roomid"));

		// セッション切れ例外処理(→logout)
		User user = (User) request.getSession().getAttribute("user");
		if (user == null) {
			error = "セッション切れの為、メッセージの投稿は出来ません。 ";
			cmd = "";
			return;
		}

		try {

			//メッセージリストを取得
			ArrayList<Message> messageList = messageDao.selectByroomid(roomid);

			//メッセージリストをリクエストスコープに登録
			request.setAttribute("messageList",messageList);
			//自身の情報を登録
			request.setAttribute("userinfo", userDao.selectByUserid(user.getUser_id()));
			//メッセージルームID登録
			request.setAttribute("roomid", roomid);

		} catch (IllegalStateException e) {
			error = "DB接続エラーの為、メッセージの表示は行えませんでした。";
			cmd = "menu";
			return;

		} finally {
			// 登録された件数を持ってフォワード
			if (error == ("")) {
				request.getRequestDispatcher("/view/messageroom.jsp").forward(request, response);

			} else {
				// 登録された件数を持ってフォワード
				request.setAttribute("error", error);
				request.setAttribute("cmd", cmd);
				request.getRequestDispatcher("/view/error.jsp").forward(request, response);
			}
		}

	}
    
    //メッセージ登録用
	public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

		// 文字エンコーディングの指定
		request.setCharacterEncoding("UTF-8");

		//パラメーターからルームID取得
		int roomid = Integer.parseInt(request.getParameter("roomid"));

		// セッション切れ例外処理(→logout)
		User user = (User) request.getSession().getAttribute("user");
		if (user == null) {
			error = "セッション切れの為、メッセージの投稿は出来ません。 ";
			cmd = "";
			return;
		}

		try {

			Message message =new Message();
			message.setMessage(request.getParameter("message"));
			message.setRoomid(roomid);
			message.setSenderId(user.getUser_id());
			messageDao.insert(message);
			//メッセージルームID登録
			request.setAttribute("roomid", roomid);

		} catch (IllegalStateException e) {
			error = "DB接続エラーの為、メッセージの投稿は行えませんでした。";
			cmd = "menu";
			return;

		} finally {
			// メッセージの登録を行い、画面へ遷移
			if (error == ("")) {
				request.getRequestDispatcher("/view/messageroom.jsp").forward(request, response);

			} else {
				// 登録された件数を持ってフォワード
				request.setAttribute("error", error);
				request.setAttribute("cmd", cmd);
				request.getRequestDispatcher("/view/error.jsp").forward(request, response);
			}
		}

	}
}

```

## 実際の動き
![2022-07-18-00-53-29 (1).gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1270216/73522db9-65b9-69a7-120e-4eb37bbd3535.gif)

## 参考文献
@OgawaNorihiroさんの下記の記事を参考にDBを作成しています。

https://qiita.com/OgawaNorihiro/items/6d9f85d8e89d1def4f15

## 最後に
これはあくまでも一例です。
研修時に作ったプログラムの機能を抜粋し、
本記事用に変換したものでこのままでは使うことが出来ない可能性があります。