これまでこのブログでは、JSFプロジェクトのForm値と画面遷移処理を定義したクラスをセッションスコープで定義してきたが、これを会話スコープに変更することもできる。
会話スコープとは、ブラウザとサーバーの間で何往復かする間だけオブジェクト(バッキングビーン)を存続させるスコープで、リクエストスコープより長く、セッションスコープより短いスコープとなる。また、会話スコープの開始/終了は、プログラマが明示的に指定する必要がある。
今回は、Form値と画面遷移処理を定義したクラス(InputFormAction.java)を、会話スコープに変更してみたので、そのサンプルプログラムを共有する。
前提条件
以下の記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は、以下の通り。

なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
アクションクラスの内容はそれぞれ以下の通りで、画面遷移時に、会話スコープを開始/終了する処理を追加している。
package faces;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.ConversationScoped;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.event.ComponentSystemEvent;
import javax.faces.model.SelectItem;
import javax.inject.Inject;
import javax.inject.Named;
import javax.validation.constraints.AssertTrue;
import javax.validation.constraints.Size;
import org.hibernate.validator.constraints.NotEmpty;
import common.CommonUtil;
import common.ConversationUtil;
import jpa.UserData;
import jpa.UserDataJpa;
import lombok.Data;
import lombok.ToString;
/**
* 画面のフォーム値と画面遷移メソッドを定義.
*/
@Named(value="inputFormAction")
// セッションスコープから会話スコープに変更
@ConversationScoped
@Data
@ToString(exclude={"birthMonthItems","birthDayItems","sexItems"})
public class InputFormAction implements Serializable {
// シリアルバージョンUID
private static final long serialVersionUID = 7283339629129432007L;
/** ID */
private String id;
/** 名前 */
@NotEmpty
@Size(min=1, max=10)
private String name;
/** 生年月日_年 */
private String birthYear;
/** 生年月日_月 */
private String birthMonth;
/** 生年月日_日 */
private String birthDay;
/** 性別 */
@NotEmpty(message="{sex.NotEmpty.message}")
private String sex;
/** 性別(ラベル) */
private String sexLabel;
/** メモ */
private String memo;
/** 確認チェック */
@AssertTrue
private Boolean checked;
/** 生年月日_月(選択リスト) */
private List<SelectItem> birthMonthItems;
/** 生年月日_日(選択リスト) */
private List<SelectItem> birthDayItems;
/** 性別(選択リスト) */
private List<SelectItem> sexItems;
/** UserDataテーブルへアクセスするJPA */
@Inject
private UserDataJpa userDataJpa;
/** 会話スコープマネージャー */
@Inject
private Conversation conv;
/**
* コンストラクタ生成時に選択リストの値を設定.
*/
public InputFormAction(){
// 生年月日_月(選択リスト)
birthMonthItems = new ArrayList<SelectItem>();
birthMonthItems.add(new SelectItem("", ""));
for(Integer i = 1; i <= 12; i++){
birthMonthItems.add(new SelectItem(String.valueOf(i), String.valueOf(i)));
}
// 生年月日_日(選択リスト)
birthDayItems = new ArrayList<SelectItem>();
birthDayItems.add(new SelectItem("", ""));
for(Integer i = 1; i <= 31; i++){
birthDayItems.add(new SelectItem(String.valueOf(i), String.valueOf(i)));
}
// 性別(選択リスト)
sexItems = new ArrayList<SelectItem>();
sexItems.add(new SelectItem(String.valueOf(1),"男"));
sexItems.add(new SelectItem(String.valueOf(2),"女"));
}
/**
* 入力画面への遷移(更新用).
* @return 入力画面へのパス
*/
public String toMod(){
// 会話スコープを開始
ConversationUtil.beginConv(conv);
// 選択されたIDをもつユーザーデータを取得・設定
this.setSelectItem();
// 入力画面に遷移
return "toMod";
}
/**
* 確認画面への遷移.
* @return 確認画面へのパス
*/
public String confirm(){
// 性別(ラベル)を設定
if(!CommonUtil.isBlank(sex)){
this.setSexLabel(this.getSexItems().get(
Integer.parseInt(this.getSex())-1).getLabel());
}
// 確認画面に遷移
return "confirm";
}
/**
* 入力画面に戻る.
* @return 入力画面へのパス
*/
public String back(){
// 入力画面に戻る
return "back";
}
/**
* 完了画面への遷移.
* @return 完了画面へのパス
*/
public String send(){
// 画面の入力内容を登録または更新
if(id != null){
userDataJpa.update(getUserData());
}else{
userDataJpa.regist(getUserData());
}
// 会話スコープを終了
ConversationUtil.endConv(conv);
// 完了画面への遷移
return "send";
}
/**
* 削除確認画面への遷移(更新用).
* @return 入力画面へのパス
*/
public String toDel(){
// 会話スコープを開始
ConversationUtil.beginConv(conv);
// 選択されたIDをもつユーザーデータを取得・設定
this.setSelectItem();
// 削除確認画面に遷移
return "toDel";
}
/**
* 削除確認画面から一覧画面への遷移.
* @return 一覧画面へのパス
*/
public String del(){
// 画面の入力内容を削除
userDataJpa.delete(getUserData());
// 会話スコープを終了
ConversationUtil.endConv(conv);
// 一覧画面に遷移
return "del";
}
/**
* 相関チェックを実施し、エラーの場合はエラーメッセージを表示.
* @param compSysEvent JSFシステムイベント
*/
public void validate(ComponentSystemEvent compSysEvent) {
UIComponent component = compSysEvent.getComponent();
// 生年月日の年・月・日を取得する
UIInput birthYearUI = (UIInput)component.findComponent("birthYear");
UIInput birthMonthUI = (UIInput)component.findComponent("birthMonth");
UIInput birthDayUI = (UIInput)component.findComponent("birthDay");
String birthYearSt = (String)birthYearUI.getLocalValue();
String birthMonthSt = (String)birthMonthUI.getLocalValue();
String birthDaySt = (String)birthDayUI.getLocalValue();
// 年・月・日がすべて空白値の場合はエラーメッセージを返す
if(CommonUtil.isBlank(birthYearSt) && CommonUtil.isBlank(birthMonthSt)
&& CommonUtil.isBlank(birthDaySt)){
addErrorMessage("org.hibernate.validator.constraints.NotEmpty.message"
, component);
return;
}
// 生年月日が存在しない日付の場合はエラーメッセージを返す
String dateStr = birthYearSt + CommonUtil.addZero(birthMonthSt)
+ CommonUtil.addZero(birthDaySt);
if(!CommonUtil.isCorrectDate(dateStr, "uuuuMMdd")){
addErrorMessage("date.Invalid.message", component);
}
}
/**
* 引数のメッセージKeyをもつエラーメッセージを追加.
* @param messageKey メッセージKey
* @param component JSFコンポーネント
*/
private void addErrorMessage(String messageKey, UIComponent component){
FacesContext context = FacesContext.getCurrentInstance();
String message = CommonUtil.getMessage(messageKey);
FacesMessage facesMessage = new FacesMessage(message, message);
facesMessage.setSeverity(FacesMessage.SEVERITY_ERROR);
context.addMessage(component.getClientId(), facesMessage);
context.renderResponse();
}
/**
* 登録時に利用するユーザー情報を生成.
* @return ユーザー情報
*/
private UserData getUserData(){
UserData userData = new UserData();
try{
if(this.id != null){
userData.setId(Integer.parseInt(this.id));
}
userData.setName(this.getName());
userData.setSex(this.getSex());
userData.setMemo(this.getMemo());
userData.setBirthYear(Integer.parseInt(this.getBirthYear()));
userData.setBirthMonth(Integer.parseInt(this.getBirthMonth()));
userData.setBirthDay(Integer.parseInt(this.getBirthDay()));
}catch(Exception ex){
System.err.println(ex);
}
return userData;
}
/**
* 選択されたIDをもつユーザーデータを取得・設定.
*/
private void setSelectItem(){
// リクエストパラメータの値を取得
FacesContext fc = FacesContext.getCurrentInstance();
Map<String,String> params = fc.getExternalContext().getRequestParameterMap();
String selectId = params.get("selectId");
// 選択したIDをもつユーザーデータを取得
UserData userData = userDataJpa.getById(selectId);
// フィールドの各値に取得した値を設定
if(userData != null){
id = String.valueOf(userData.getId());
name = userData.getName();
birthYear = String.valueOf(userData.getBirthYear());
birthMonth = String.valueOf(userData.getBirthMonth());
birthDay = String.valueOf(userData.getBirthDay());
sex = userData.getSex();
sexLabel = this.getSexItems().get(
Integer.parseInt(userData.getSex())-1).getLabel();
memo = userData.getMemo();
}
}
}package faces;
import java.io.Serializable;
import java.util.List;
import javax.enterprise.context.Conversation;
import javax.enterprise.context.SessionScoped;
import javax.inject.Inject;
import javax.inject.Named;
import common.ConversationUtil;
import jpa.UserData;
import jpa.UserDataJpa;
import lombok.Getter;
/**
* USER_DATAリスト取得処理を定義.
*/
//「更新」「削除」リンクを機能させるため、RequestScopedからSessionScopeに変更
@Named(value="userListAction")
@SessionScoped
public class UserListAction implements Serializable{
// シリアルバージョンUID
private static final long serialVersionUID = -8890511854883241114L;
/** USER_DATAリスト */
@Getter
private List<UserData> userDataList;
/** UserDataテーブルへアクセスするJPA */
@Inject
private UserDataJpa userDataJpa;
/** 会話スコープマネージャー */
// InputFormActionクラスの会話スコープを制御するために宣言
@Inject
private Conversation conv;
/** 一覧画面の初期表示処理 */
public void initialize(){
// USER_DATAテーブルの全件を取得する
userDataList = userDataJpa.getAll();
}
/**
* 入力画面への遷移(追加用).
* @return 入力画面へのパス
*/
public String toAdd(){
// 会話スコープを開始
ConversationUtil.beginConv(conv);
return "add";
}
/**
* 一覧画面への遷移.
* @return 一覧画面へのパス
*/
public String toList(){
// 会話スコープを終了
ConversationUtil.endConv(conv);
// 初期表示処理を呼び出し、一覧画面に遷移する
this.initialize();
return "list";
}
}アクションクラスで呼び出される、会話スコープを開始・終了するクラスの内容は、以下の通り。
package common;
import javax.enterprise.context.Conversation;
/**
* 会話スコープを開始・終了するクラス.
*/
public class ConversationUtil {
/**
* 会話スコープを開始.
* @param conv 会話スコープマネージャー
*/
public static void beginConv(Conversation conv){
// 会話スコープを開始していない場合は開始する
if(conv.isTransient()){
conv.begin();
}
}
/**
* 会話スコープを終了.
* @param conv 会話スコープマネージャー
*/
public static void endConv(Conversation conv){
// 会話スコープを終了していない場合は終了する
if(!conv.isTransient()){
conv.end();
}
}
}その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/javaee-jsf-conversation/demoJsf
サンプルプログラムの実行
サンプルプログラムの実行結果は以下の通りで、会話スコープを利用して、user_dataテーブルの更新・削除ができることが確認できる。
1) 実行前のuser_dataテーブルの中身は、以下の通り。
select * from USER_DATA order by ID asc

2) GlassFishサーバーを起動後、Webブラウザ上で「http:// (ホスト名):(ポート番号)/(Webアプリケーションのプロジェクト名)/」とアクセスすると、以下のように、user_dataテーブルの中身が一覧画面(list.html)に表示されることが確認できる。ここでID=4の「削除」リンクを押下する。

3) 以下のように、削除確認画面が表示されID=4のデータが表示されることが確認できる。また、会話スコープが開始したため、URLにパラメータcidが付与されたことが確認できる。ここで「戻る」ボタンを押下する。

4) 以下のように、一覧画面が表示され、先ほど削除リンクを押下したID=4のデータが表示されることが確認できる。また、会話スコープが終了したため、URLにパラメータcidが付与されていないことが確認できる。ここで再度「削除」リンクを押下する。

5) 削除確認画面で「送信」ボタンを押下する。なお、会話スコープが再度開始したため、URLにパラメータcidが付与され、cidが1増えていることが確認できる。

6) 以下のように、一覧画面に遷移し、ID=4のデータが削除されていることが確認できる。また、会話スコープが終了したため、URLにパラメータcidが付与されていないことが確認できる。ここでID=3の「更新」リンクを押下する。

7) 以下のように、入力画面が表示されID=3のデータが表示されることが確認できる。また、会話スコープが開始したため、URLにパラメータcidが付与され、cidが1増えていることが確認できる。ここで「戻る」ボタンを押下する。

8) 以下のように、一覧画面が表示され、先ほど更新リンクを押下したID=3のデータが表示されることが確認できる。また、会話スコープが終了したため、URLにパラメータcidが付与されていないことが確認できる。ここで再度「更新」リンクを押下する。

9) 入力画面でデータを編集後、「確認」ボタンを押下する。なお、会話スコープが再度開始したため、URLにパラメータcidが付与され、cidが1増えた状態で画面遷移することが確認できる。


11) 以下のように、完了画面に遷移することが確認できる。また、会話スコープが終了したため、URLにパラメータcidが付与されていないことが確認できる。ここで「一覧画面に戻る」ボタンを押下する。

12) 以下のように、一覧画面に遷移し、更新後のデータが表示されることが確認できる。

13) 実行後のuser_dataテーブルの中身は以下の通りで、削除・更新内容が反映されていることが確認できる。
select * from USER_DATA order by ID asc

要点まとめ
- 会話スコープとは、ブラウザとサーバーの間で何往復かする間だけオブジェクト(バッキングビーン)を存続させるスコープで、会話スコープの開始/終了は、プログラマが明示的に指定する必要がある。






