セッションを利用してみよう

12.2 セッションを利用してみよう

 本節ではJSP/サーブレットに用意されたスコープの1つであるセッションの使い方について学習していきます。

12.2.1 データの登録と取得

 セッションにデータを登録する場合、まず始めにセッションオブジェクトを生成します。

書式:セッションオブジェクトの生成

 HttpServletRequestインタフェースに定義されたgetSession()メソッドを利用し、HttpSessionオブジェクトを取得します。

書式:セッションへの登録

 HttpSessionインタフェースのオブジェクトに用意されたsetAttribute()メソッドを利用することで、セッションへデータを登録することができます。メソッドの引数には、登録するデータとそのデータにつける名前を渡します。

書式:セッションからのデータの取得

 HttpSessionインタフェースのオブジェクトに用意されたgetAttribute()メソッドを利用することで、セッションに登録されたデータを取得することができます。メソッドの引数には、登録されたオブジェクトの名前を渡します。リクエストスコープと同じように、登録されたデータはObject型となるため、セッションから取得したデータは登録する前の型でキャストを行う必要があります。

 では実際にセッションを利用するプログラムを作成してみましょう

セッションを利用しサーブレットからJSPへデータを渡すプログラム

 セッションへのデータの登録とデータの取得方法について学習します。9.2.1項で作成した「サーブレット内で文字列をリクエストスコープに登録し、そのメッセージをJSPで受け取り画面に表示するプログラム」を、セッションを利用したプログラムに改修し、データが表示できることを確認します。

実行結果

アプリケーション構成

① ソース・フォルダ :web_basic/WEB-INF/src
② パッケージ :ch12
③ 名前 :UseSessionServlet
④ スーパークラス :javax.servlet.http.HttpServlet
⑤ アクセスURL :http://localhost:8080/web_basic/UseSessionServlet

➢ UseSessionServlet.java
package ch12;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class UseSessionServlet extends HttpServlet {

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

		//セッションオブジェクトの生成
		HttpSession session = request.getSession();
		//セッションへのデータの登録
		session.setAttribute("bookTitle", "JSPサーブレットの本");
		request.getRequestDispatcher("/view/ch12/useSession.jsp").forward(request, response);
	}
}
➢ web.xml
<servlet>
	<servlet-name>UseSessionServletMapping</servlet-name>
	<servlet-class>ch12.UseSessionServlet</servlet-class>
</servlet> 
<servlet-mapping>
	<servlet-name>UseSessionServletMapping</servlet-name>
	<url-pattern>/UseSessionServlet</url-pattern>
</servlet-mapping>

① 親フォルダの入力または選択 :web_basic/view/ch12
② ファイル名 :useSession.jsp
③ アクセスURL :UseSessionServlet.javaからの画面遷移でアクセスされる

➢ useSession.jsp
<%@page contentType= "text/html; charset=UTF-8" %>

<%
//セッションからのデータの取得
String title = (String)session.getAttribute("bookTitle");
%>

<html>
	<head>
		<title>セッションを利用したプログラム</title>
	</head>
	<body>
		<%= title %>
	</body>
</html>

解説
 このプログラムは、11.2.1項で作成したリクエストスコープを使ったプログラムを、セッションを利用したプログラムに改修したものです。プログラムの動き自体は変わりませんが、データの登録先がリクエストスコープからセッションに変わっています。
 UseSessionServlet.javaではセッションにデータを登録します。
 まず13行目で、HttpSessionインタフェースのオブジェクトを生成しています。セッションオブジェクトの生成にはnew演算子を利用するのではなくrequestオブジェクトのgetSession()メソッドを利用します。
   13:HttpSession session = request.getSession();

 15行目で、実際にデータをセッションに登録しています。setAttribute()メソッドの第1引数に「bookTitle」という文字列を渡し、第2引数には「JSPサーブレットの本」という文字列を渡しています。リクエストスコープを利用するプログラムと異なる点は、メソッドを呼び出すためのオブジェクトが違うということだけで、メソッドの使い方自体は全く変わりません。
   15:session.setAttribute("bookTitle", "JSPサーブレットの本");

図 12.2.1: セッションへのデータ登録の流れ

 このときにセッションへ登録されるデータも、リクエストスコープと同様にObject型として登録されます。

図 12.2.2:セッションへのデータ登録時の型変換

 16行目でフォワードを利用してuseSession.jspに転送を行います。
   16:request.getRequestDispatcher("/view/ch12/useSession.jsp").forward(request, response);

 useSession.jspへの処理の転送後は、4行目で登録された文字列を取得しています。
 getAttribute()メソッドの引数に「bookTitle」という文字列を渡しています。この引数の文字列がセッションから取得するデータの名前になります。このデータの取得方法についても、リクエストスコープとの違いはメソッドを呼び出すためのオブジェクトが違うという点だけで、メソッドの使い方自体は全く変わりません。
   4:String message = (String)session.getAttribute("SessionMessage");

図 12.2.3: セッションからのデータ取得の流れ

 リクエストスコープと同様に、登録されたデータはObject型で取得されます。そのため、取得されたデータを適切に扱うためには、型をセッションへ登録される前の型に明示的に変換する必要があります。

図 12.2.4:セッションからデータを取得する際の型変換

 実行結果からわかるように、サーブレット内でセッションに登録した文字列をJSPで表示することができています。このように、リクエストスコープと同じく、セッションを使った場合でも複数のファイル間でデータの共有が行えます。
 今回のプログラムではデータの登録先をリクエストスコープからセッションへ変更しただけなので、2つのスコープの違いがわからないと思います。次のプログラムでは2つのスコープの違いについて確認して行きましょう。

リクエストスコープとセッションの違いを確認するプログラム

 このプログラムは、アクセスされた回数をセッションとリクエストに登録し画面に表示します。リクエストスコープとセッションに登録されたデータが保持されている期間の違いについて確認しましょう。

実行結果

アプリケーション構成

① ソース・フォルダ :web_basic/WEB-INF/src
② パッケージ :ch12
③ 名前 :UsefulRangeServlet
④ スーパークラス :javax.servlet.http.HttpServlet
⑤ アクセスURL :http://localhost:8080/web_basic/UsefulRangeServlet

➢ UsefulRangeServlet.java
package ch12;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class UsefulRangeServlet extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
 	throws ServletException, IOException {
 
 		//セッションオブジェクトの生成
 		HttpSession session = request.getSession();
 		//セッションからのデータの取得
 		Integer sessionCount = (Integer)session.getAttribute("SessionCount");
 		//セッションデータがない場合の初期化処理
 		if(sessionCount == null){
 			sessionCount = 0;
 		}
 		//カウント変数をインクリメントしセッションへの登録
 		session.setAttribute("SessionCount", ++sessionCount);
 
 		//リクエストからのデータの取得
 		Integer requestCount = (Integer)request.getAttribute("RequestCount");
 		//リクエストデータがない場合の初期化処理
 		if(requestCount == null){
 			requestCount = 0;
 		}
 		//カウント変数をインクリメントしリクエストへ登録
 		request.setAttribute("RequestCount", ++requestCount);
 
 		request.getRequestDispatcher("/view/ch12/usefulRange.jsp").forward(request, response);
 	}
 }
➢ web.xml
<servlet>
	<servlet-name>UsefulRangeServletMapping</servlet-name>
	<servlet-class>ch12.UsefulRangeServlet</servlet-class>
</servlet> 
<servlet-mapping>
	<servlet-name>UsefulRangeServletMapping</servlet-name>
	<url-pattern>/UsefulRangeServlet</url-pattern>
</servlet-mapping>

① 親フォルダの入力または選択 :web_basic/view/ch12
② ファイル名 :usefulRange.jsp
③ アクセスURL :UsefulRangeServlet.javaからの画面遷移でアクセスされる

➢ usefulRange.jsp
<%@page contentType= "text/html; charset=UTF-8" %>

<%
//セッションからのデータの取得
Integer sessionCount = (Integer)session.getAttribute("SessionCount");
//リクエストからのデータの取得
Integer requestCount = (Integer)request.getAttribute("RequestCount");
%>

<html>
	<head>
		<title>有効範囲の違いを確認するプログラム</title>
	</head>
	<body>
		アクセス回数(セッション):
		<%= sessionCount %><br>
		アクセス回数(リクエスト):
		<%= requestCount %><br>
		<form action="<%= request.getContextPath() %>/UsefulRangeServlet" method="get">
			<input type="submit" value="リロード">
		</form>
	</body>
</html>

解説
 リロードボタンを押すたびにアクセス回数をカウントして表示するプログラムです。ただし、リロードボタンを押すと分かりますが、正しくカウントされるのはセッションのカウントのみです。
 次の図のようにリクエストのカウントは毎回1と表示されます。

図 12.2.5: カウント表示の動き

 このプログラムのポイントは、各スコープの有効範囲の違いです。リクエストスコープに登録されたデータは1ページが表示されるたびに消えてしまうのに対し、セッションに登録されたデータはそのまま引き継がれます。
 まずは、セッションに登録する方の処理について確認してみましょう。
 UsefulRangeServlet.javaでは、13行目~21行目でセッションに登録されたアクセス回数のカウントアップを行なっています。
 まず始めに、13行目ではセッションオブジェクトの生成を行なっています。
   13:HttpSession session = request.getSession();

 次に、15行目ではセッションにSessionCountという名前で登録されたアクセス回数を取得しています。最初のアクセス時にはデータは登録されていないため、null値が取得され、2回目以降のアクセス時にはアクセス回数-1の値が取得されます。
   15:Integer sessionCount = (Integer)session.getAttribute("SessionCount");

 17行目~19行目では、セッションから取得した値がnullかどうかを判定しています。nullだった場合は、初回アクセスとなるため、変数sessionCountに初期値として「0」を代入しています。
   17:if(sessionCount == null){
   18: sessionCount = 0;
   19:}

 21行目ではセッションから取得したアクセス数をインクリメントして増やし、同じ名前で再度セッションに登録し直しています。これにより、アクセスするたびに回数がカウントアップされる仕組みになっています。
   21:session.setAttribute("SessionCount", ++sessionCount);

 次に、リクエストスコープに登録する方の処理を見てみましょう。
 UsefulRangeServlet.javaでは、24行目~30行目までがアクセス回数の取得から初回の判定、カウントアップ処理、データの再登録などを行なっています。ソースコードを確認すると、セッションの登録先や登録名が異なるだけで、同じ処理を行なっているのが分かります。
   24:Integer requestCount = (Integer)request.getAttribute("RequestCount");
   26:if(requestCount == null){
   27: requestCount = 0;
   28:}
   30:request.setAttribute("RequestCount", ++requestCount);

 各スコープへのデータの登録後は25行目でJSPへの転送を行います。
 UsefulRangeServlet.javaでは、24行目~30行目までがアクセス回数の取得から初回の判定、カウントアップ処理、データの再登録などを行なっています。ソースコードを確認すると、セッションの登録先や登録名が異なるだけで、同じ処理を行なっているのが分かります。
   25:request.getRequestDispatcher("/view/ch12/usefulRange.jsp").forward(request, response);

 転送後のusefulRange.jspでは各スコープから値を取得し画面に表示しています。
 また、画面上には再度のアクセスのリロードボタンが定義されていて、自分自身のサーブレットであるUsefulRangeServletに遷移するように記述されています。これにより、ボタンを押すたびに自分自身をリロードする仕組みになっています。
 UsefulRangeServlet.javaでは、24行目~30行目までがアクセス回数の取得から初回の判定、カウントアップ処理、データの再登録などを行なっています。ソースコードを確認すると、セッションの登録先や登録名が異なるだけで、同じ処理を行なっているのが分かります。
   19:<form action="<%= request.getContextPath() %>/UsefulRangeServlet" method="get">

 各スコープに登録されたデータは以下の表ようになります。

図 12.2.6: 各スコープのデータの流れ

 リクエストスコープではページが表示されるたびにデータが削除されるのに対し、セッションでは次のページを表示する際にも、データが保持されています。このように、スコープには各々に決まった保持期間が存在します。そのため、適切なスコープを利用することが大切です。

12.2.2 セッションの破棄

 セッションに登録されたデータは、接続が続いている間保持されます。しかし、状況によってはセッションのデータを削除したい場合があります。そのような場合は、セッションの破棄を行います。

 Sessionオブジェクトのinvalidate()メソッドを利用することで、セッションを破棄することができます。
 では、セッションのデータを破棄するプログラムを作成してみましょう

セッションのデータを破棄するプログラム

 入力フォームから入力されたデータをセッションに登録し、別のJSPに表示します。表示用のJSPページから入力フォームへ戻る際、任意でセッションデータを破棄することができ、破棄しない場合は入力値をフォーム内に引き継ぎます。また、破棄した場合はフォーム内が空の状態で表示することができます。

実行結果

アプリケーション構成

① 親フォルダの入力または選択 :web_basic/view/ch12
② ファイル名 :useSessionForm1.jsp
③ アクセスURL :http://localhost:8080/web_basic/view/ch12/useSessionForm1.jsp

➢ useSessionForm1.jsp
<%@page contentType= "text/html; charset=UTF-8" %>

<%
//各データをセッションから取得
String name = (String)session.getAttribute("name");
String age = (String)session.getAttribute("age");
String sex = (String)session.getAttribute("sex");
//セッションデータがない場合の初期化処理
if(name == null){
 	name = "";
 }
 if(age == null){
 	age = "";
 }
 %>
 
 <html>
 	<head>
 		<title>セッションを破棄するプログラム</title>
 	</head>
 	<body>
 		<form action="<%= request.getContextPath() %>/UseSessionFormServlet1" method="get">
 			お名前:
 			<input type="text" name="name" value=<%= name %>><br>
 			年齢:
 			<input type="text" name="age" value=<%= age %>><br>
 			性別:
 			<%if(sex == null || sex.equals("男性")){%>
 				<input type="radio" name="sex" value="男性" checked>男性
 				<input type="radio" name="sex" value="女性">女性<br>
 			<%}else{%>
 				<input type="radio" name="sex" value="男性">男性
 				<input type="radio" name="sex" value="女性" checked>女性<br>
 			<%}%>
 			<input type="submit" value="送信">
 		</form>
 	</body>
 </html>

① ソース・フォルダ :web_basic/WEB-INF/src
② パッケージ :ch12
③ 名前 :UseSessionFormServlet1
④ スーパークラス :javax.servlet.http.HttpServlet
⑤ アクセスURL :useSessionForm1.jspからの画面遷移でアクセスされる

➢ UseSessionFormServlet1.java
package ch12;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class UseSessionFormServlet1 extends HttpServlet {

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

		//フォームから入力されたパラメータの取得
		request.setCharacterEncoding("UTF-8");
		String name = request.getParameter("name");
		String age = request.getParameter("age");
		String sex = request.getParameter("sex");

		//セッションへのデータの登録
		HttpSession session = request.getSession();
		session.setAttribute("name", name);
		session.setAttribute("age", age);
		session.setAttribute("sex", sex);

		request.getRequestDispatcher("/view/ch12/useSessionForm2.jsp").forward(request, response);
	}
}
➢ web.xml
<servlet>
	<servlet-name>UseSessionFormServlet1Mapping</servlet-name>
	<servlet-class>ch12.UseSessionFormServlet1</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>UseSessionFormServlet1Mapping</servlet-name>
	<url-pattern>/UseSessionFormServlet1</url-pattern>
</servlet-mapping>

① 親フォルダの入力または選択 :web_basic/view/ch12
② ファイル名 :useSessionForm2.jsp
③ アクセスURL :UseSessionFormServlet1.javaからの画面遷移でアクセスされる

➢ useSessionForm2.jsp
<%@page contentType= "text/html; charset=UTF-8" %>

<%
//各データをセッションから取得
String name = (String)session.getAttribute("name");
String age = (String)session.getAttribute("age");
String sex = (String)session.getAttribute("sex");
%>

 <html>
 	<head>
 		<title>セッションを破棄するプログラム</title>
 	</head>
 	<body>
 		お名前:<%= name %><br>
 		年齢:<%= age %><br>
 		性別:<%= sex %><br>
 		<form action="<%= request.getContextPath() %>/UseSessionFormServlet2" method="get">
 			<input type="submit" value="セッションを破棄して戻る">
 		</form>
 		<form action="<%= request.getContextPath() %>/view/ch12/useSessionForm1.jsp" method="get">
 			<input type="submit" value="セッションを破棄せず戻る">
 		</form>
 
 	</body>
 </html>

① ソース・フォルダ :web_basic/WEB-INF/src
② パッケージ :ch12
③ 名前 :UseSessionFormServlet2
④ スーパークラス :javax.servlet.http.HttpServlet
⑤ アクセスURL :useSessionForm2.jspからの画面遷移でアクセスされる

➢ UseSessionFormServlet2.java
package ch12;

import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class UseSessionFormServlet2 extends HttpServlet {

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

		//セッションオブジェクトの取得
		HttpSession session = request.getSession();
		//セッションがある場合、セッションを破棄
		if(session != null){
			session.invalidate();
		}

		request.getRequestDispatcher("/view/ch12/useSessionForm1.jsp").forward(request, response);
	}
}
➢ web.xml
<servlet>
	<servlet-name>UseSessionFormServlet2Mapping</servlet-name>
	<servlet-class>ch12.UseSessionFormServlet2</servlet-class>
</servlet>
<servlet-mapping>
	<servlet-name>UseSessionFormServlet2Mapping</servlet-name>
	<url-pattern>/UseSessionFormServlet2</url-pattern>
</servlet-mapping>

解説
 このプログラムは、フォームから入力されたデータをセッションに登録し画面に一覧で表示しています。
 表示用の画面には「セッションを破棄して戻る」と「セッションを破棄せず戻る」の2つのボタンがあり、どちらもフォーム画面を表示しますが、「セッションを破棄して戻る」を選択した場合は、セッションに登録された入力データが全て削除されます。また、「セッションを破棄せず戻る」を選択した場合は、入力したデータをフォーム画面に引き継いて表示します。

 フォーム画面のuseSessionForm1.jspでは、セッションデータを取得しフォーム部品の値として設定しています。セッションデータがない場合は、各フォーム部品の値を空白に設定しています。
   5:String name = (String)session.getAttribute("name");
   6:String age = (String)session.getAttribute("age");
   7:String sex = (String)session.getAttribute("sex");
   9:if(name == null){
   10: name = "";
   11:}
   12:if(age == null){
   13: age = "";
   14:}

 また、HTML内にif文を埋め込むことで、ラジオボタンのチェック選択部分を動的に設定しています。
   28:<%if(sex == null || sex.equals("男性")){%>
   29: <input type="radio" name="sex" value="男性" checked>男性
   30: <input type="radio" name="sex" value="女性">女性<br>
   31:<%}else{%>
   32: <input type="radio" name="sex" value="男性">男性
   33: <input type="radio" name="sex" value="女性" checked>女性<br>
   34:<%}%>

 この設定によって、入力されたデータを破棄しない場合はデータを引き継ぐことができます。

 入力されたデータを表示するuseSessionForm2.jspでは2種類のボタンによって転送先を変えています。
 「セッションを破棄して戻る」を選択した場合はUseSessionFormServlet2.java、「セッションを破棄せず戻る」を選択した場合はuseSessionForm1.jspへ転送されます。
   18:<form action="<%= request.getContextPath() %>/UseSessionFormServlet2" method="get">
   19: <input type="submit" value="セッションを破棄して戻る">
   20:</form>
   21:<form action="<%= request.getContextPath() %>/view/ch12/useSessionForm1.jsp" method="get">
   22: <input type="submit" value="セッションを破棄せず戻る">
   23:</form>

 このプログラムの遷移図は次のようになります。

図 12.2.7: セッションを破棄するプログラムの遷移図

 上の遷移図のように、「セッションを破棄して戻る」が選択された場合、UseSessionFormServlet2.javaへ転送された後、最初のフォーム画面へ転送されます。セッションの破棄はこのUseSessionFormServlet2.javaで行われています。
 UseSessionFormServlet2.javaの15行目から17行目でセッションの破棄が行われます。
 13行目でセッションオブジェクトを取得し、15行目ではセッションオブジェクトが存在するかどうかを判定しています。セッションオブジェクトがすでにnullになっていた場合、破棄しようとするとエラーになってしまうため、注意が必要です。16行目ではinvalidate()メソッドを利用してセッションを破棄します。
   13:HttpSession session = request.getSession();
   15:if(session != null){
   16: session.invalidate();
   17:}

 UseSessionForm2Servlet.java内でセッションが破棄されると、最初のフォーム画面に戻った際にセッションのデータを取得できなくなるため、何もデータが入力されていない状態に戻る仕組みになっています。

 このように、セッションの破棄を行うことでセッション内のデータを全て削除することができます。

12.2.3 セッションのタイムアウト

 ショッピングサイトなどで、ログインを行った後に何もせずに時間が経過したとき、「接続の有効時間が切れました。再度ログインしてください。」というようなメッセージが表示されることがあります。
 セッションのデータは同一の接続が続いている間保持されますが、サーバ側では、実際にブラウザが閉じられたかどうかを正確に知る方法がありません。そのため、最後のアクセス時間からの経過時間をチェックし、一定時間が過ぎた場合に、セッションを削除する機能が備わっています。この仕組みのことをセッションタイムアウトと呼びます。
 このセッションタイムアウトには主に2つの目的があります。
 1つ目は、サーバのリソースを効率的に利用することです。 セッションのデータは接続ごとに管理されるため、接続数が増え過ぎるとリソースが不足し、サービスの提供ができなくなる恐れがあります。そこで、セッションタイムアウトによって、システムを一定時間利用していないユーザのセッションを削除しリソースを解放します。
 2つ目は、セキュリティ対策です。ショッピングサイトなどでログインしたまま長時間PCから離れた場合、第三者がその PC を不正に利用し個人情報などが盗まれてしまう可能性があります。そのようなことを防ぐため、セッションタイムアウトを短めに設定することができます。

 セッションタイムアウトの設定は、web.xmlに<session-config>要素の子要素である<session-timeout>で指定することができます。

書式:セッションタイムアウトの指定

 上記の書式ではセッションタイムアウトを1分に指定しています。
 なお、Tomcat8の場合、セッションタイムアウトのデフォルトは30分となっています。

 では、セッションタイムアウトを設定し、その時間でセッションが削除されることを以下の手順に沿って確認してみましょう。

① web.xmlの編集
 web.xmlの<servlet-mapping>要素の下に次の記述を追加してください。
 この設定によって、1分でセッションが削除されるようになります。

➢ web.xml
<session-config>
	<session-timeout>1</session-timeout>
</session-config>

② Tomcatの再起動
 web.xmlを編集した後はTomcatの再起動を忘れずにおこなってください。

③ 確認用のWebアプリケーションにアクセス
 セッションタイムアウトの確認には前項で作成したプログラムを利用します。
 ブラウザから以下のURLにアクセスしましょう。
 http://localhost:8080/web_basic/view/ch12/useSessionForm1.jsp

図 12.2.8: 確認用Webアプリケーションの起動

④ セッションデータを登録しタイムアウトまで待機
 お名前、年齢、性別を入力し、送信ボタンをクリックします。
 その後、表示されるデータの一覧画面で1分間何もせずに待ちます。

図 12.2.9: データの入力と一覧表示

⑤ 「セッションを破棄せず戻る」クリック
 1分が経過したら「セッションを破棄せず戻る」ボタンをクリックします。

図 12.2.10: セッションタイムアウトの確認

 セッションタイムアウトによってセッションが削除され、入力フォームの値が消えているのが確認できます。


NEXT>> 12.3 本章のまとめ