JakartaEE(旧称:JavaEE)で利用可能なJPA(Java Persistence API)を利用すると、データの参照・追加に加え、データの更新・削除も実施することができる。
今回は、作成済のJSFプロジェクトに、削除・更新機能を追加してみたので、そのサンプルプログラムを共有する。
前提条件
以下の記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は、以下の通り。
なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
一覧画面の内容は以下の通りで、更新リンク・削除リンクを追加している。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:c="http://xmlns.jcp.org/jsp/jstl/core"> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>一覧画面</title> </h:head> <h:body> <!-- 初期表示時に、UserListActionクラスのinitializeメソッドを呼び出す --> <f:metadata> <f:viewAction action="#{userListAction.initialize()}"/> </f:metadata> <p>ユーザーデータテーブル(user_data)の全データ</p> <!-- ユーザーデータが存在しない場合 --> <c:if test="${empty userListAction.userDataList}"> ユーザーデータはありません。 </c:if> <!-- ユーザーデータが存在する場合 --> <c:if test="${not empty userListAction.userDataList}"> <table border="1" cellpadding="5"> <tr> <th>ID</th> <th>名前</th> <th>生年月日</th> <th>性別</th> <th></th> <th></th> </tr> <c:forEach var="userData" items="#{userListAction.userDataList}"> <tr> <td><h:outputText value="#{userData.id}" /></td> <td><h:outputText value="#{userData.name}" /></td> <td><h:outputText value="#{userData.birthYear}年 #{userData.birthMonth}月 #{userData.birthDay}日" /></td> <td> <c:choose> <c:when test="${userData.sex == '1'}">男</c:when> <c:when test="${userData.sex == '2'}">女</c:when> <c:otherwise>不明</c:otherwise> </c:choose> </td> <td> <h:form> <h:commandLink value="更新" action="#{inputFormAction.toMod()}"> <f:param name="selectId" value="#{userData.id}" /> </h:commandLink> </h:form> </td> <td> <h:form> <h:commandLink value="削除" action="#{inputFormAction.toDel()}"> <f:param name="selectId" value="#{userData.id}" /> </h:commandLink> </h:form> </td> </tr> </c:forEach> </table> </c:if> <br/><br/> <!-- データ追加ボタンを追加 --> <h:form> <h:commandButton value="データ追加" action="#{userListAction.toAdd()}" /> </h:form> </h:body> </html>
また、新設した削除確認画面の内容は以下の通り。
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://java.sun.com/jsf/html"> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>削除確認画面</title> <h:outputStylesheet library="css" name="demoJsf.css"/> </h:head> <h:body> <p>下記内容を削除してよろしいでしょうか?問題なければ「送信」ボタンを押下してください。</p><br/> <h:form> <table border="0"> <tr> <td align="left" valign="top">名前:</td> <td><h:outputText value="#{inputFormAction.name}" /></td> </tr> <tr> <td align="left" valign="top">生年月日:</td> <td><h:outputText value="#{inputFormAction.birthYear}年 #{inputFormAction.birthMonth}月 #{inputFormAction.birthDay}日" /></td> </tr> <tr> <td align="left" valign="top">性別:</td> <td><h:outputText value="#{inputFormAction.sexLabel}" /></td> </tr> <tr> <td align="left" valign="top">メモ:</td> <td><h:outputText value="#{inputFormAction.memo}" styleClass="lineBreakFormat" /></td> </tr> </table> <br/> <h:commandButton value="送信" action="#{inputFormAction.del()}" /> <!-- 戻るボタンを追加 --> <!-- その際、フォームの各項目値の入力チェックを省くため、immediate="true"を追加 --> <h:commandButton value="戻る" action="#{userListAction.toList()}" immediate="true" /> </h:form> </h:body> </html>
さらに、一覧画面でリンクが押下されたときの処理を記載したクラスは以下の通りで、選択されたIDをもつユーザーデータを取得・設定後、入力画面または削除確認画面に遷移する処理を記載している。
package faces; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.enterprise.context.SessionScoped; 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 jpa.UserData; import jpa.UserDataJpa; import lombok.Data; import lombok.ToString; /** * 画面のフォーム値と画面遷移メソッドを定義. */ // @Namedアノテーションは、JSFのXHTMLファイルから#{inputFormAction}で // Javaクラスを参照できるようにしている(→バッキングビーン) // @SessionScopedアノテーションは、このバッキングビーンの生存期間を // セッションに設定している @Named(value="inputFormAction") @SessionScoped // 以下はLombokのアノテーション //「@Data」アノテーションを付与すると、このクラス内の全フィールドに対する // Getterメソッド・Setterメソッドにアクセスができる @Data //「@ToString」アノテーションを用いて、exclude属性で指定した項目以外の値を、 // toStringメソッド呼出時に出力することができる @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; /** * コンストラクタ生成時に選択リストの値を設定. */ 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(){ // 選択された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()); } // セッション情報の破棄 FacesContext.getCurrentInstance().getExternalContext().invalidateSession(); // 完了画面への遷移 return "send"; } /** * 削除確認画面への遷移(更新用). * @return 入力画面へのパス */ public String toDel(){ // 選択されたIDをもつユーザーデータを取得・設定 this.setSelectItem(); // 削除確認画面に遷移 return "toDel"; } /** * 削除確認画面から一覧画面への遷移. * @return 一覧画面へのパス */ public String del(){ // 画面の入力内容を削除 userDataJpa.delete(getUserData()); // セッション情報の破棄 FacesContext.getCurrentInstance().getExternalContext().invalidateSession(); // 一覧画面に遷移 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.SessionScoped; import javax.inject.Inject; import javax.inject.Named; 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; /** 一覧画面の初期表示処理 */ public void initialize(){ // USER_DATAテーブルの全件を取得する userDataList = userDataJpa.getAll(); } /** * 入力画面への遷移(追加用). * @return 入力画面へのパス */ public String toAdd(){ return "add"; } /** * 一覧画面への遷移. * @return 一覧画面へのパス */ public String toList(){ // 初期表示処理を呼び出し、一覧画面に遷移する this.initialize(); return "list"; } }
なお、スコープを変更した理由については、以下のサイトを参照のこと。
https://gloryof.hatenablog.com/entry/20120901/1346481378
さらに、faces-config.xmlの定義は以下の通りで、一覧画面から更新・削除するためのルートを追加している。
<?xml version="1.0" encoding="UTF-8"?> <faces-config xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd" version="2.2"> <!-- 一覧画面から入力画面(追加用・更新用)・削除確認画面への遷移 --> <!-- ただし、リダイレクトさせるために、redirectタグを追加 --> <navigation-rule> <from-view-id>/list.xhtml</from-view-id> <navigation-case> <from-outcome>add</from-outcome> <to-view-id>/input.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>toMod</from-outcome> <to-view-id>/input.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>toDel</from-outcome> <to-view-id>/confirm_delete.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> <!-- 入力画面から確認画面・一覧画面への遷移 --> <!-- ただし、リダイレクトさせるために、redirectタグを追加 --> <navigation-rule> <from-view-id>/input.xhtml</from-view-id> <navigation-case> <from-outcome>confirm</from-outcome> <to-view-id>/confirm.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>list</from-outcome> <to-view-id>/list.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> <!-- 確認画面から完了画面・入力画面への遷移 --> <!-- ただし、リダイレクトさせるために、redirectタグを追加 --> <navigation-rule> <from-view-id>/confirm.xhtml</from-view-id> <navigation-case> <from-outcome>send</from-outcome> <to-view-id>/complete.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>back</from-outcome> <to-view-id>/input.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> <!-- 完了画面から一覧画面への遷移 --> <!-- ただし、リダイレクトさせるために、redirectタグを追加 --> <navigation-rule> <from-view-id>/complete.xhtml</from-view-id> <navigation-case> <from-outcome>list</from-outcome> <to-view-id>/list.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> <!-- 削除確認画面から一覧画面への遷移 --> <!-- ただし、リダイレクトさせるために、redirectタグを追加 --> <navigation-rule> <from-view-id>/confirm_delete.xhtml</from-view-id> <navigation-case> <from-outcome>del</from-outcome> <to-view-id>/list.xhtml</to-view-id> <redirect/> </navigation-case> <navigation-case> <from-outcome>list</from-outcome> <to-view-id>/list.xhtml</to-view-id> <redirect/> </navigation-case> </navigation-rule> <!-- サポートしている言語を日本語に指定 --> <application> <locale-config> <default-locale>ja_JP</default-locale> <supported-locale>ja</supported-locale> <supported-locale>ja_JP</supported-locale> </locale-config> </application> </faces-config>
また、JPAクラスの内容は以下の通りで、一件取得処理(getById)・更新処理(update)・削除処理(delete)を追加している。
package jpa; import java.util.List; import javax.enterprise.context.RequestScoped; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.transaction.Transactional; /** * USER_DATAテーブルへアクセスするJPA. */ @RequestScoped public class UserDataJpa { @PersistenceContext private EntityManager em; /** * USER_DATAテーブルに引数のデータを追加する. * @param userData */ @Transactional public void regist(UserData userData){ // UserDataテーブルのid最大値を取得 Integer maxId = em.createQuery("SELECT MAX(u.id) FROM UserData u" , Integer.class).getSingleResult(); // id最大値がNULLの場合は、0に変換 maxId = (maxId == null) ? 0 : maxId; // USER_DATAテーブルのIDを設定後、登録処理を実行 userData.setId(maxId + 1); em.persist(userData); em.flush(); } /** * USER_DATAテーブルの全件を取得する. * @return USER_DATAテーブル全件のリスト */ @Transactional public List<UserData> getAll(){ // UserDataテーブルのデータを全件取得し返却 List<UserData> uList = em.createQuery("FROM UserData u ORDER BY u.id ASC" , UserData.class).getResultList(); // 取得したデータを返却 return uList; } /** * 選択したIDをもつUSER_DATAテーブルの値を取得する. * @param selectId 選択したID * @return 選択したIDをもつUSER_DATAテーブルの値 */ @Transactional public UserData getById(String selectId) throws NumberFormatException{ // UserDataテーブルの選択したIDをもつデータを取得 Integer intId = Integer.parseInt(selectId); UserData userData = em.createQuery("FROM UserData u WHERE u.id = :intId" , UserData.class) .setParameter("intId", intId) .getSingleResult(); // 取得したデータを返却 return userData; } /** * 引数のデータをもつUSER_DATAテーブルのデータを更新する. * @param userData */ @Transactional public void update(UserData userData){ // IDが設定されている場合、更新 if(userData.getId() != null){ em.merge(userData); em.flush(); } } /** * 引数のデータをもつUSER_DATAテーブルのデータを削除する. * @param userData */ @Transactional public void delete(UserData userData){ // IDが設定されている場合、削除 if(userData.getId() != null){ em.remove(this.getById(String.valueOf(userData.getId()))); em.flush(); } } }
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/javaee-jsf-delete-update/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のデータが表示されることが確認できる。ここで「戻る」ボタンを押下する。
4) 以下のように、一覧画面が表示され、先ほど削除リンクを押下したID=4のデータが表示されることが確認できる。ここで再度「削除」リンクを押下する。
6) 以下のように、一覧画面に遷移し、ID=4のデータが削除されていることが確認できる。ここでID=3の「更新」リンクを押下する。
7) 以下のように、入力画面が表示されID=3のデータが表示されることが確認できる。ここで「戻る」ボタンを押下する。
8) 以下のように、一覧画面が表示され、先ほど更新リンクを押下したID=3のデータが表示されることが確認できる。ここで再度「更新」リンクを押下する。
11) 以下のように、完了画面に遷移することが確認できる。ここで「一覧画面に戻る」ボタンを押下する。
12) 以下のように、一覧画面に遷移し、更新後のデータが表示されることが確認できる。
13) 実行後のuser_dataテーブルの中身は以下の通りで、削除・更新内容が反映されていることが確認できる。
select * from USER_DATA order by ID asc
要点まとめ
- JakartaEE(旧称:JavaEE)で利用可能なJPA(Java Persistence API)を利用すると、データの参照・追加に加え、データの更新・削除も実施することができる。