JakartaEE(旧称:JavaEE)には、EJB(Enterprise JavaBeans)という、サーバ上で動作するアプリケーションをソフトウェア部品(コンポーネント)を組み合わせて開発・実行できるようにする仕組みがあり、データベースアクセス処理などでEJBを利用できる。
今回は、作成済のJSFプロジェクトで、EJBを利用してみたので、そのサンプルプログラムを共有する。
なお、EJBについては、以下のサイトを参照のこと。
 https://atmarkit.itmedia.co.jp/fjava/keyword/jkey/jkey03.html
前提条件
以下の記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は、以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
JPAクラスの内容は以下の通りで、先頭にEJBのステートレスセッションBeanを示す@Statelessアノテーションを付与すると共に、トランザクション属性をREQUIREDに指定する@TransactionAttributeアノテーションを付与している。
package jpa;
import java.util.List;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
/**
 * USER_DATAテーブルへアクセスするJPA.
 */
// EJBのステートレスセッションBean(各クライアントに固有の値を保持しない)
@Stateless
public class UserDataJpa {
    @PersistenceContext
    private EntityManager em;
    /**
     * USER_DATAテーブルに引数のデータを追加する.
     * @param userData
     */
    // トランザクション属性をREQUIRED(トランザクションが開始していれば
    // そのトランザクションで、トランザクションが開始していない場合は
    // 新しいトランザクションを開始して実行)に設定
    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    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テーブル全件のリスト
     */
    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    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テーブルの値
     */
    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    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
     */
    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    public void update(UserData userData){
    	// IDが設定されている場合、更新
        if(userData.getId() != null){
            em.merge(userData);
            em.flush();
        }
    }
    /**
     * 引数のデータをもつUSER_DATAテーブルのデータを削除する.
     * @param userData
     */
    @TransactionAttribute(value = TransactionAttributeType.REQUIRED)
    public void delete(UserData userData){
    	// IDが設定されている場合、削除
        if(userData.getId() != null){
            em.remove(this.getById(String.valueOf(userData.getId())));
            em.flush();
        }
    }
}なお、EJBのステートレスセッションBeanについては、以下のサイトを参照のこと。
 https://www.itmedia.co.jp/enterprise/0402/13/epn03.html
また、トランザクション属性のREQUIREDについては、以下のサイトを参照のこと。
 https://itpfdoc.hitachi.co.jp/manuals/3020/30203M0360/EM030204.HTM
さらに、先ほどのJPAクラスを呼び出しているクラスの内容は以下の通りで、JPAクラスをインジェクトする箇所に@EJBアノテーションを付与している。
package faces;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.ejb.EJB;
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.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 */
    // EJBのステートレスセッションBeanをインジェクト
    @EJB
    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.ejb.EJB;
import javax.enterprise.context.SessionScoped;
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 */
    // EJBのステートレスセッションBeanをインジェクト
    @EJB
    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://github.com/purin-it/java/tree/master/javaee-jsf-ejb/demoJsf
サンプルプログラムの実行
サンプルプログラムの実行結果は、以下の記事の「サンプルプログラムの実行結果」と同じ結果となる。
要点まとめ
- JakartaEE(旧称:JavaEE)には、EJB(Enterprise JavaBeans)という、サーバ上で動作するアプリケーションをソフトウェア部品(コンポーネント)を組み合わせて開発・実行できるようにする仕組みがあり、データベースアクセス処理などでEJBを利用できる。
 





