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