EJBを使用したトランザクション管理
今回はEJB3.1を使ったトランザクション管理についてです。
【前提条件】
[環境]
- JDK 1.7.0_07
- Glassfish 3.1.2.2
- PostgreSQL 9.1(JDBC:postgresql-9.1-901.jdbc4.jar)
【サンプルコード】
[テーブル]
前回、使用した「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を指定するとロールバックを行うようになります。