EJBを使用したトランザクション管理

今回はEJB3.1を使ったトランザクション管理についてです。

【前提条件】

[環境]
[その他]
  • JPAの設定が完了している*1

【概要】

JavaEE6でEJB3.1を使うことにより、
トランザクション管理をEJBに任せることができます。

EJB3.1ではメソッド単位で
トランザクションの開始、コミット、ロールバックを行ってくれます。

【サンプルコード】

[テーブル]

前回、使用した「sales」テーブルを使います。
テーブルの定義は前回と変わっていません。

CREATE TABLE sales
(
    user_id character varying(10) NOT NULL,
    goods_code character varying(5) NOT NULL,
    number_of_articles integer,
    CONSTRAINT sales_primary_key PRIMARY KEY (user_id , goods_code )
)
[Entity]

エンティティのクラスです。
前回のSalesクラスと変わっていません。

package jp.glory.persistence.entity;

import java.io.Serializable;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "sales")
public class Sales implements Serializable {

    private static final long serialVersionUID = 1067393173514970977L;

    @Id
    @Column(name = "user_id")
    private String userId = null;

    @Id
    @Column(name = "goods_code")
    private String goodsCode = null;

    @Column(name = "number_of_articles")
    private int numberOfArticles = 0;

    // アクセサメソッドは省略
}
[persistence.xml]

persistence.xmlも特別な設定はしていません。
使用するエンティティクラスを定義しています。

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
    xmlns="http://java.sun.com/xml/ns/persistence"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
    http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">

    <persistence-unit name="blog" transaction-type="JTA">
        <jta-data-source>jdbc/blog</jta-data-source>
        <class>jp.glory.persistence.entity.Sales</class>
    </persistence-unit>
</persistence>
[EJB]

EJBのクラスです。
サンプルでは内部で例外を発生させるようにしています。
例外の発生はフラグで管理しています。

package jp.glory.application;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;

import jp.glory.persistence.entity.Sales;

@Stateless
public class SalesEntryApplication {

    @PersistenceContext(unitName = "blog")
    private EntityManager manager = null;

    public void entry(final Sales sales, final boolean isError) {

        manager.persist(sales);

        if (isError) {

            throw new RuntimeException("EJB Error!");
        }
    }
}

EJB3.1の詳細については省きますが、
今回はステートレスとしてEJBを作成します。
ステートレスとして定義するにはjavax.ejb.Statelessアノテーションを使用します。

前回までメソッド内で作成しいた
EntityManagerですが、EJBと連携するとインジェクションできるようになります。
javax.persistence.PersistenceContextアノテーションを使用すると
EJBがインジェクションされたときにあわせてEntityManagerもインジェクションされます。

unitName属性に使用するpersistence-unitの名前を指定します。

[登録クラス]

登録を行うクラスです。
ManagedBean上ではEJB内で例外を発生させるフラグと
JSF内で例外を発生させるフラグを持ちます。

package jp.glory.ui;

import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

import jp.glory.application.SalesEntryApplication;
import jp.glory.persistence.entity.Sales;

@ManagedBean(name = "salesEntryPage")
@RequestScoped
public class SalesEntryPage {

    @EJB
    private SalesEntryApplication application = null;

    private String userId = null;

    private String goodsCode = null;

    private Integer numberOfArticles = null;

    private boolean ejbError = false;

    private boolean jsfError = false;

    public void entry() {

        final Sales salesData = new Sales();
        salesData.setUserId(userId);
        salesData.setGoodsCode(goodsCode);
        salesData.setNumberOfArticles(numberOfArticles);

        application.entry(salesData, ejbError);

        if (jsfError) {

            throw new RuntimeException("JSF Error!!");
        }

        // 登録が終わったのでデータ初期化
        userId = null;
        goodsCode = null;
        numberOfArticles = null;
        jsfError = false;
    }

作成したEJBをインジェクションするためには
javax.ejb.EJBアノテーションを使用します。
EJBアノテーションにはいくつか属性がありますが、デフォルト値で動作します。

[ページ]

登録用のページです。

<?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:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html">
    <f:view>
        <h:head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <title>登録サンプル</title>
        </h:head>
        <h:body>
            <h:form id="entryForm">
                <div>
                    ユーザID:<h:inputText id="userId" value="#{salesEntryPage.userId}" />
                </div>
                <div>
                    商品コード:<h:inputText id="goodsCode" value="#{salesEntryPage.goodsCode}" />
                </div>
                <div>
                    個数:<h:inputText id="numberOfArticles" value="#{salesEntryPage.numberOfArticles}" />
                </div>
                <div>
                    EJB内エラー:<h:selectBooleanCheckbox id="ejbError" value="#{salesEntryPage.ejbError}" />
                </div>
                <div>
                    JSF内エラー:<h:selectBooleanCheckbox id="jsfError" value="#{salesEntryPage.jsfError}" />
                </div>
                <div>
                    <h:commandButton action="#{salesEntryPage.entry()}" value="登録" />
                </div>
            </h:form>
        </h:body>
    </f:view>
</html>

【実行】

作成したページは↓のような画面になります。

EJB内エラーにチェックをした状態で
登録ボタンをクリックするとDBに登録されません。
作成したEJB内部でEntityManager#persistメソッドが実行されていますが、
EJBロールバックを指示したため、ロールバックが行われています。

JSF内エラーにチェックをした状態で
登録ボタンをクリックするとDBに登録されています。
作成したEJBのメソッドの実行が終わった段階で、
EJBがコミットをする指示したため、コミットが行われています。

ロールバックされる例外について】

ロールバックされる例外はRuntimeExceptionと
java.rmi.RemoteExceptionのサブクラスです。

EJB3.1の仕様書の「14.2.2System Exceptions」に記述されています。
The Java Community Process(SM) Program - communityprocess - mrel

Exceptionを継承したクラスでロールバックさせる場合は
javax.ejb.ApplicationExceptionアノテーションを使用します。

package jp.glory.common.exception;

import javax.ejb.ApplicationException;

@ApplicationException(rollback = true)
public class HogeException extends Exception {

}

rollback属性にtrueを指定するとロールバックを行うようになります。