JakartaEE(JavaEE)

JSFプロジェクトでEJB(Enterprise JavaBeans)を利用してみた

JakartaEE(旧称:JavaEE)には、EJB(Enterprise JavaBeans)という、サーバ上で動作するアプリケーションをソフトウェア部品(コンポーネント)を組み合わせて開発・実行できるようにする仕組みがあり、データベースアクセス処理などでEJBを利用できる。

今回は、作成済のJSFプロジェクトで、EJBを利用してみたので、そのサンプルプログラムを共有する。

なお、EJBについては、以下のサイトを参照のこと。
https://atmarkit.itmedia.co.jp/fjava/keyword/jkey/jkey03.html

前提条件

以下の記事の実装が完了していること。

JSFプロジェクトで更新・削除機能を追加してみたJakartaEE(旧称:JavaEE)で利用可能なJPA(Java Persistence API)を利用すると、データの参照・追加に...

サンプルプログラムの作成

作成したサンプルプログラムの構成は、以下の通り。

なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。

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

サンプルプログラムの実行

サンプルプログラムの実行結果は、以下の記事の「サンプルプログラムの実行結果」と同じ結果となる。

JSFプロジェクトで更新・削除機能を追加してみたJakartaEE(旧称:JavaEE)で利用可能なJPA(Java Persistence API)を利用すると、データの参照・追加に...

要点まとめ

  • JakartaEE(旧称:JavaEE)には、EJB(Enterprise JavaBeans)という、サーバ上で動作するアプリケーションをソフトウェア部品(コンポーネント)を組み合わせて開発・実行できるようにする仕組みがあり、データベースアクセス処理などでEJBを利用できる。