EJBを使う
LastUpdate : 12/11/03
Java EE6になり、EJBは3.1となりました。
MDBやリモート・インターフェイスなどを使用しなければ(EJB Liteの中にある機能しか使わない場合)warの中にEJBを配置出来るようになったらしい。
それ以外で、3.0から何が変わったのかは、調べてません。。。
ここでのサンプルは3.0も含めて、基本的なEJBの使い方の説明となります。
もくじ
EJBの種類について
ステートレスBeanの簡単なサンプル
ステートフルBeanの簡単なサンプル
シングルトン・セッションBean.
「セッション・コンテキスト」と「環境ネーミング・コンテキスト」について
非同期処理
タイマサービス
インターセプターのサンプル
トランザクション管理について(CMT)
トランザクション管理について(BMT)
セッションBeanとタイマサービスがあります。
セッションBeanの中には以下のような3種類のものがあります。
セッションBean |
ステートレス・セッション・Bean |
ステートフル・セッション・Bean |
シングルトン・セッション・Bean |
実際使うとなると、ステートレス・セッション・Beanしか使わないケースがほとんどだと思います(ステートレス以外を使う必要のあるケースが思い浮かばない)。
シングルトン・セッション・Beanは、シングルトンと言ってますが、1アプリサーバで1インスタンス生成されます。なので、クラスタ環境だと複数インスタンスが生成されます。残念ですね(まぁ、そうだとは思うがw)。ただ、同時実行をさせなくするなどのオプションがありますので、特定の処理では使いどころがあるかもしれません。
タイマサービスは、何時間間隔で・・・や、指定した日に・・・って情報を設定してやると、指定されたタイミングで処理が自動実行されます。
これもまた、あんまり使いどころを思い浮かびません(こういうケースは、TWSやJP1みたいなミドルウェアからバッチ呼び出しで機能を実現するのがメジャーなやり方かと。まぁ、要件次第かねぇ)。
極論ですが、ステートレス・セッション・Beanだけ覚えておけば良いんじゃないかなと思います。
本ページでは、EJBにはローカル接続とリモート接続2種類ありますが、ローカル接続だけしか扱いません。
リモート接続については、ほんの少しの改修で実現可能ですし、そもそも私がリモート接続なんてつかわねーYO!って思ってるので、端折りました。
簡単なステートレスBeanのサンプルです。
簡単といっても、ステートレスBeanとしては、これ以外書くことがないので・・・。ほんと、EJB3.0になり簡単に作成出来るようになりました。
ステートレスBeanの注意点として、以下のものがあります。
たとえば、ステートレスBeanクラスに、setNameメソッドと、getNameメソッドがあったとします。
そして、このステートレスBeanのインスタンスを作成し、setNameメソッドを呼び出し名前を設定したとします。次に、getNameメソッドで値を取得しようとした場合、さきほどセットした名前が取得できるとは限りません。こういった使い方はステートレスBeanではNGです。
EJBコンテナが処理をしようとする場合、setNameメソッドが呼び出され時に処理をさせたステートレスBeanのインスタンスと、getNameメソッドが呼ばれたときに処理をさせるステートレスBeanのインスタンスを一致させる保障を行いません。これが、ステートレスBeanの特徴で、この保障を行わない分、処理速度がステートフルBeanと比較すると早いです。
Study1EJB.java |
package alctail.ejb; import javax.ejb.Stateless; import alctail.bean.ResultBean; @Stateless public class Study1EJB { public ResultBean createMessage() { ResultBean result = new ResultBean(); result.setMsg("わんわんお!"); return result; } } |
私は、上記EJBを呼び出して、動作確認するために以下のようなRESTサービスを作成し、ローカルで動作確認をしました。
Study1Service.java |
package alctail.service; import javax.annotation.PostConstruct; import javax.ejb.EJB; import alctail.bean.ResultBean; import alctail.ejb.Study1EJB; public class Study1Service { @EJB private Study1EJB study; @PostConstruct private void initialize() { System.out.println("Study1Serviceの初期化をしました。"); } public String service() { ResultBean resultBean = study.createMessage(); return resultBean.getMsg(); } } |
上記Serviceクラスを作成し呼び出す、RESTクラス。
EJBStudy1Rest.java |
package alctail.rest; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import alctail.service.Study1Service; @Path("/EJBStudy1") public class EJBStudy1Rest { @Inject private Study1Service study1Service; /** 簡単なEJBのサンプル(ステートレスBean). */ @GET @Path("/study1") @Produces("text/plain") public String study1() { return study1Service.service(); } } |
上記のようにして使えば動くはずです。
ステートフルBeanは、EJBコンテナが処理させるステートフルBeanのインスタンスを、クライアント毎に一致させます。
以下のような例でもうまく動作します。
Study2EJB.java |
package alctail.ejb; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import javax.ejb.Remove; import javax.ejb.Stateful; import javax.ejb.StatefulTimeout; import alctail.bean.ResultBean; @Stateful @StatefulTimeout(value=30, unit=TimeUnit.SECONDS) public class Study2EJB { private int num; public ResultBean createMessage() { ResultBean result = new ResultBean(); try { Thread.sleep(2*1000); } catch(Exception e){} result.setMsg("わんわんお! : "+num); return result; } public ResultBean createMessage2() { ResultBean result = new ResultBean(); try { Thread.sleep(2*1000); } catch(Exception e){} num++; result.setMsg("にゃんにゃんお : "+num); return result; } @PostConstruct private void init() { System.out.println("■------------------------------------------------------"); System.out.println("初期化します。。。(Study2EJB)\n"); } @Remove public void remove() { System.out.println("ひゃっはー!"); } } |
上記EJBに対して、以下のような呼び出しを行っています。
ここでポイントとなるのが、createMessage2メソッドを何度も呼び出していること。この処理を実行するとEJBが持つフィールドがインクリメントされますが、呼び出しのたびにEJBコンテナが前回使用したインスタンスを使い処理を実行するので、処理結果が正常に数値がインクリメントされた文言が取得できます。
また、ステートフルBeanを使う場合は、JNDI名でlookupしてきたほうが良いらしい。というか、このやりかたじゃないとうまく動かなかったよ。。。まぁ、今後ともステートフルBeanは使う予定がないので、原因については深く考えない。
Study2Service.java |
package alctail.service; import javax.naming.Context; import javax.naming.InitialContext; import alctail.ejb.Study2EJB; /** * JBoss AS7.1.2で、@Removeのついているメソッドを自分で呼ぶと、「JBAS014101: Failed to find SFSB instance with session ID 」 * とINFOレベルのログがでるが、バグらしい。→ https://issues.jboss.org/browse/AS7-5077 * きにしないでOK。 */ public class Study2Service { /** ステートフルBeanの動作確認. */ public String service1() { Context ctx = null; Study2EJB ejb = null; try { ctx = new InitialContext(); ejb = (Study2EJB)ctx.lookup("java:/global/EJBStudy1/Study2EJB!alctail.ejb.Study2EJB"); } catch(Exception e){throw new RuntimeException("むりぽ");} StringBuilder builder = new StringBuilder(); builder.append(ejb.createMessage().getMsg()+"\n"); builder.append(ejb.createMessage2().getMsg()+"\n"); builder.append(ejb.createMessage2().getMsg()+"\n"); builder.append(ejb.createMessage2().getMsg()+"\n"); builder.append(ejb.createMessage2().getMsg()+"\n"); builder.append(ejb.createMessage2().getMsg()+"\n"); ejb.remove(); return builder.toString(); } } |
以下のようなRESTサービスを作り、動作確認を行いました。
EJBStudy2Rest.java |
package alctail.rest; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import alctail.service.Study2Service; /** * study1をアクセス後、study2にアクセスすることで、ステートフルEJBの動作確認ができます */ @Path("/EJBStudy2") public class EJBStudy2Rest { @Inject private Study2Service study2Service; /** 簡単なEJBのサンプル(ステートフルBean). */ @GET @Path("/study1") @Produces("text/plain") public String study1() { return study2Service.service1(); } } |
シングルトンBeanは、javaで作るシングルトンと同じく、クラスタを超えて1つのインスタンスにはなれませんので、注意。
また、メソッドの同時実行を管理することが出来ます。
■コンテナ管理の同時実行(CMC)
同時アクセス制御を、アノテーションはXMLディスクリプタで制御する(何も指定しないと、デフォルトでこちらになる)
デフォルトでは、排他ロックとなり、複数呼び出しは行えない。
@Lock(LockType.READ):同時アクセス可能
@Lock(LockType.WRITE):排他呼び出しとなる(デフォルト)
呼び出したほうは処理が終わるまで待たされる。
このアノテーションはクラス・メソッドに記述でき、メソッド単位でREAD/WRITEを指定したい場合は、メソッドにアノテーションを付与する。
■Bean管理の同時実行(BMC)
同時アクセス制御は、実装に委ねる(処理側で排他制御などをする必要がある)。
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)アノテーションを付与することで指定できる。
また、@AccessTimeoutを付与することで挙動の指定が可能。
@AccessTimeout(0):同時アクセスを禁止する。同時アクセスすると例外が飛ぶ
@AccessTimeout(-1):アクセス可能になるまで無制限に待つ
@AccessTimeout(value=5,unit=TimeUnit.SECONDS):5秒だけ待つ指定もできる。
Study3EJB.java |
package alctail.ejb; import javax.ejb.Lock; import javax.ejb.LockType; import javax.ejb.Singleton; import javax.ejb.Startup; /** * シングルトン・セッションBean. * 「@Startup」をつけておけば、初期化処理が行われるタイミングは、通常、アクセスされたときだが、 * アプリケーション起動時に初期化処理が実行される。 * * また、このクラスが初期化する前に、初期化が終わってなければいけないシングルトン・セッションBeanがある場合 * 「@DependsOn」アノテーションを使用し、該当のシングルトン・セッションBeanを指定することによって初期化を正常に行うことができる。 */ @Singleton @Startup @Lock(LockType.WRITE) public class Study3EJB { /** * 年齢を変換する. * @param realAge 実際の年齢 * @return 対外向け年齢 */ public int convertAge(int realAge) { if (realAge < 25) { return realAge; } else { return 17; } } /** * 定説的年齢を返す * @return 年齢 */ public int defaultAge() { return 17; } } |
以下のようなServiceクラスを作成し、RESTから呼び出して動作確認を行いました。
Study3Service.java |
package alctail.service; import javax.ejb.EJB; import alctail.ejb.Study3EJB; public class Study3Service { @EJB private Study3EJB ejb; public String service1(int age) { String result = ejb.convertAge(age)+"歳です☆"; return result; } } |
EJBStudy3Rest.java |
package alctail.rest; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import alctail.service.Study3Service; @Path("/EJBStudy3") public class EJBStudy3Rest { @Inject private Study3Service study3Service; @GET @Path("/study1/{age}") @Produces("text/plain") public String study1(@PathParam("age") int age) { return study3Service.service1(age); } } |
ejb-jar.xmlに設定値を外だしすることもできます(環境ネーミング・コンテキスト)。
warにEJBを配置している場合、ejb-jar.xmlは、WEB-INFディレクトリ直下に配置することとなります。
Study4EJB.java |
package alctail.ejb; import javax.annotation.Resource; import javax.ejb.SessionContext; import javax.ejb.Stateless; /** * 「セッション・コンテキスト」と「環境ネーミング・コンテキスト」について * * warにEJBを入れている場合は、ejb-jar.xmlファイルをWEB-INFの中に配置すること。 */ @Stateless public class Study4EJB { /** セッション・コンテキストをDIできる */ @Resource private SessionContext ctx; /** ejb-jar.xmlの中に定義されている設定情報を取得できる */ @Resource(name="EternalAge") private String age; /** 同上 */ @Resource(name="SelfTukkomi") private String tukkomi; public String service1() { // セッションコンテキストを、@ResourceでDIできます。 // 以下はてきとーな使用例・・・ Study1EJB ejb = (Study1EJB)ctx.lookup("java:global/EJBStudy1/Study1EJB!alctail.ejb.Study1EJB"); String msg1 = ejb.createMessage().getMsg(); // 「環境ネーミング・コンテキスト」から取得した値を使う String msg2 = "私の年齢は" + age + "です。" + tukkomi; return msg1 + " / " + msg2; } } |
以下のファイル(ejb-jar.xml)ファイルをWEB-INFディレクトリの直下に配置します。
ejb-jar.java |
<?xml version="1.0" encoding="UTF-8"?> <ejb-jar version="3.1" xmlns:javaee="http://java.sun.com/xml/ns/javaee" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_1.xsd "> <enterprise-beans> <session> <ejb-name>Study4EJB</ejb-name> <ejb-class>alctail.ejb.Study4EJB</ejb-class> <env-entry> <env-entry-name>EternalAge</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>17</env-entry-value> </env-entry> <env-entry> <env-entry-name>SelfTukkomi</env-entry-name> <env-entry-type>java.lang.String</env-entry-type> <env-entry-value>おいおい</env-entry-value> </env-entry> </session> </enterprise-beans> </ejb-jar> |
以下のようなServiceクラスを作成し、RESTから呼び出して動作確認を行いました。
Study4Service.java |
package alctail.service; import javax.ejb.EJB; import alctail.ejb.Study4EJB; public class Study4Service { @EJB private Study4EJB ejb; public String service1() { return ejb.service1(); } } |
EJBStudy4Rest.java |
package alctail.rest; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import alctail.service.Study4Service; @Path("/EJBStudy4") public class EJBStudy4Rest { @Inject private Study4Service study4Service; @GET @Path("/study1/") @Produces("text/plain") public String study1() { return study4Service.service1(); } } |
時間がとてもかかる処理など、応答を待っているとタイムアウトしてしまいます(月次集計処理など)。
その場合、処理を実行し、応答をすぐ返すというEJBを作ることが可能です。また、あとで、戻り値を取得することも可能です(Futureクラス)
service2メソッドは、処理に時間がかかっている風を出すため、Sleep処理をいれてあります。
Study5EJB.java |
package alctail.ejb; import java.util.concurrent.Future; import javax.ejb.AsyncResult; import javax.ejb.Asynchronous; import javax.ejb.Stateless; import alctail.bean.ResultBean; /** * 非同期処理. * 「@Asynchronous」を付与すると、非同期呼び出しが可能となる。 * クラス・メソッドに付与できる。 */ @Stateless @Asynchronous public class Study5EJB { /** 戻り値無しにすることも可能 */ public void service1() { System.out.println("Study5EJB#service1を開始します! >>>>>>>>>>>>>>>>"); try {Thread.sleep(5*1000);}catch(Exception e){} System.out.println("Study5EJB#service1を終了します! <<<<<<<<<<<<<<<<"); } /** 戻り値が必要な場合は、Futureクラスを使用する */ public Future<ResultBean> service2(String msg) { System.out.println("Study5EJB#service2を開始します! >>>>>>>>>>>>>>>>"); try {Thread.sleep(5*1000);}catch(Exception e){} ResultBean resultBean = new ResultBean(); resultBean.setMsg("ゆがみねぇな"); Future<ResultBean> result = new AsyncResult<ResultBean>(resultBean); System.out.println("Study5EJB#service1を終了します! <<<<<<<<<<<<<<<<"); return result; } } |
以下のようなServiceクラスを作成し、RESTから呼び出して動作確認を行いました。
Study5Service.java |
package alctail.service; import java.util.concurrent.ExecutionException; import javax.ejb.EJB; import alctail.bean.ResultBean; import alctail.ejb.Study5EJB; public class Study5Service { @EJB private Study5EJB ejb; public String service1() { ejb.service1(); return "非同期呼び出しのservice1を実行"; } public String service2() { try { System.out.println("Study5Service#service2 start -----------------"); // getメソッドを呼び出し、戻り値を取得する(戻り値が取得するまで処理がブロックされる) ResultBean resultBean = ejb.service2("うひwwww").get(); System.out.println("Study5Service#service2 end -----------------"); return resultBean.getMsg(); } catch(ExecutionException e) { return "エラー1"; } catch(InterruptedException e) { return "エラー2"; } } } |
EJBStudy5Rest.java |
package alctail.rest; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import alctail.service.Study5Service; @Path("/EJBStudy5") public class EJBStudy5Rest { @Inject private Study5Service study5Service; /** 非同期呼び出しののサンプル(EJB戻り値無し). */ @GET @Path("/study1/") @Produces("text/plain") public String study1() { return study5Service.service1(); } /** 非同期呼び出しののサンプル(EJB戻り値有り). */ @GET @Path("/study2/") @Produces("text/plain") public String study2() { return study5Service.service2(); } } |
指定した時間に、指定のメソッドが自動実行されるEJBを作成できます。
以下のようなEJBを作成すると、デプロイ後、指定の時間が来たら、メソッドが実行されます。
Study6EJB.java |
package alctail.ejb; import javax.ejb.Schedule; import javax.ejb.Schedules; import javax.ejb.Stateless; /** * タイマサービス. * * コンテナ起動時に、インスタンスが作成され、指定されたタイミングでメソッドが実行されます * タイマサービスが指定できるのは、ステートレスBean/シングルトン/MDBです。 * * 「永続的なタイマ」と「非永続的なタイマ」があります。 * 「永続的なタイマ」は、サーバーがシャットダウンしても、情報が保持され、サーバーが再度起動するとシャットダウンがなかったかのように動くらしい。 * 「非永続的なタイマ」は、サーバーが起動する時点がスタート地点で、前回の情報は引き継がない・・・んだと思う。 * デフォルトで 「永続的なタイマ」です。 * persistent = falseと指定すると「非永続的なタイマ」になります。 * * サンプルでは@Scheduleの属性に単一のものしか指定していないが、年月日や曜日など細かく指定可能(cronみたいに) */ @Stateless public class Study6EJB { /** 1個のタイマを指定する場合の書き方. */ @Schedule(second="*/5", minute="*", hour="*", persistent = false) public void showMsg1() { System.out.println("5秒に一回メッセージを出力するよ!"); } /** 「@Schedules」を使うと、複数指定できる */ @Schedules({@Schedule(second="*/10", minute="*", hour="*", persistent = false), @Schedule(second="*/15", minute="*", hour="*")}) public void showMsg2() { System.out.println("10秒に一回と、15秒に一回メッセージを出力するよ!"); } } |
アノテーションで指定する時間の書式はほぼ、cronと同じです。
設定次第で、処理を割り込ませることが出来ます。インターセプターの概念は、ぐぐればいっぱい出てくるので省略。
少し長いので、テーマを3つに分けました。
・インスタンス生成時に指定の処理をさせる(@PostConstruct)
・インターセプター(@AroundInvoke)
・インターセプター(@Interceptors)
インスタンス生成時に指定の処理をさせる(@PostConstruct)
以下は、インターセプターではないですが、今まで説明していなかったので、ここでちょっくらサンプルとして、書きます。
アノテーションでインスタンス生成時に指定のメソッドを呼び出す・・・ということが可能です。今まで何気なく使ってましたが、「@PostConstruct」などが該当します。
@PostConstruct インスタンスが作成される際、呼び出されるメソッドを指定する
@PreDestroy インスタンスを破棄する際、呼び出されるメソッドを指定する
@PrePassivate ステートフルBeanが、インスタンスを非活性化する際呼び出されるメソッドを指定する
@PostActivate ステートフルBeanが、インスタンスを活性化する際呼び出されるメソッドを指定する
インスタンスの管理はコンテナが行うので、インスタンス生成タイミングや、破棄のタイミングはコンテナ次第です。なので、用途は限られるかと・・・。
Study7EJB.java |
package alctail.ejb; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.ejb.Stateless; import alctail.bean.ResultBean; @Stateless public class Study7EJB { /** インスタンス作成の際に呼び出されます */ @PostConstruct public void init() { System.out.println("初期化します"); } public ResultBean service1() { System.out.println("呼び出されました!"); ResultBean resultBean = new ResultBean(); resultBean.setMsg("未知のエリアへ"); return resultBean; } /** インスタンス破棄時に呼び出されます */ @PreDestroy public void destroy() { System.out.println("終了します"); } } |
クラス内に「@AroundInvoke」をつけたメソッドを作成すると、クラス内のメソッドが呼び出されるたびに、@AroundInvokeがついたメソッドが割り込みで実行されるようになります。
あんまり使いどころは無いかも^^;
デバッグ時ぐらい?
Study8_1EJB.java |
package alctail.ejb; import javax.ejb.Stateless; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; import alctail.bean.ResultBean; @Stateless public class Study8_1EJB { public ResultBean service1() { ResultBean resultBean = new ResultBean(); resultBean.setMsg("強くなりたい・・・"); return resultBean; } @AroundInvoke private Object aroundInvoke(InvocationContext ic) throws Exception { System.out.println("Study8_1EJB#aroundInvokeメソッド が呼び出されました(start)"); try { return ic.proceed(); // 呼び出されたメソッドを呼び出す } finally { System.out.println("Study8_1EJB#aroundInvokeメソッド が呼び出されました(finish)"); } } } |
上記の例では、service1メソッドを呼び出すと、最初に「"Study8_1EJB#aroundInvokeメソッド が呼び出されました(start)」というメッセージが出力されるようになります。その後、service1の処理が実行されます(ic.proceedと記述している箇所がservice1を呼び出している処理に該当)
インターセプターとして、クラスを指定することが出来ます。
今回はそのサンプルです。
Study8_2EJB.java |
package alctail.ejb; import javax.ejb.Stateless; import javax.interceptor.ExcludeClassInterceptors; import javax.interceptor.Interceptors; import alctail.bean.ResultBean; import alctail.ejb.interceptor.Log1Interceptor; import alctail.ejb.interceptor.Log2Interceptor; /** * インターセプターのサンプル * 「@Interceptors」はクラスにも、メソッドにも付与可能。クラスに付与した場合、メソッドすべてに適用される。 * この場合、適用外とするためには「@ExcludeClassInterceptors」を対象のメソッドに付与する。 */ @Stateless @Interceptors({Log1Interceptor.class, Log2Interceptor.class}) public class Study8_2EJB { public ResultBean service1(String msg) { ResultBean resultBean = new ResultBean(); resultBean.setMsg("わんわんお"+msg); return resultBean; } @ExcludeClassInterceptors public ResultBean service2() { ResultBean resultBean = new ResultBean(); resultBean.setMsg("わふぅ"); return resultBean; } } |
上記ソース内で指定しているLog1Interceptor.classとLog2Interceptor.classは以下のような実装となっています。
Log1Interceptor.java |
package alctail.ejb.interceptor; import java.util.Date; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; /** * Interceptorとして定義するクラス * (Logと名前がついているが、ここでは簡略化のため標準出力に出力する) */ public class Log1Interceptor { @AroundInvoke public Object log(InvocationContext ic) throws Exception { System.out.println("[Log1Interceptor#log] メソッドを開始します!"+new Date()); try { return ic.proceed(); } finally { System.out.println("[Log1Interceptor#log] メソッドを終了します!"+new Date()); } } } |
Log2Interceptor.java |
package alctail.ejb.interceptor; import java.util.Date; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; /** * Interceptorとして定義するクラス * (Logと名前がついているが、ここでは簡略化のため標準出力に出力する) */ public class Log2Interceptor { @AroundInvoke public Object showArgs(InvocationContext ic) throws Exception { System.out.println("[Log2Interceptor#log] メソッドを開始します!"+new Date()); System.out.println("method : "+ic.getMethod().getName()); // 呼び出し元メソッド System.out.println("args count : "+ic.getParameters().length); // メソッドの引数 for (Object obj : ic.getParameters()) { System.out.println(" "+obj.toString()); } try { return ic.proceed(); } finally { System.out.println("[Log2Interceptor#log] メソッドを終了します!"+new Date()); } } } |
EJBコンテナにトランザクションを任せることも、自分で制御することも可能です。
EJBコンテナにトランザクションを任せることをCMTと呼びます。自動でトランザクション管理を行ってくれるので、単純な更新系処理の場合はこちらが向いています。
ここで、重要になるのは「@TransactionAttribute」アノテーションです。このJavaDocはこちらへ。
よく使用するのは以下のものではないかと思います。
REQUIRED → トランザクションが開始されている場合は、そのトランザクション引き続き使用して処理を続ける。トランザクションが開始されていない場合、開始をする。
REQUIRES_NEW → トランザクションが開始されていても、別箇新しくトランザクションを作成し処理を行う。今行う処理が、現行のトランザクションに影響を与えたくない場合に使用する。
MANDATORY → トランザクションが開始されている状態で呼び出されることを前提とする。開始されていない場合、例外(EJBTransactionRequiredException)を投げる。
REQUIREDがデフォルト値となっていますので、変更の必要がない場合、@TransactionAttributeは不要です。
Study9EJB.java |
package alctail.ejb; import java.util.List; import javax.annotation.Resource; import javax.ejb.ApplicationException; import javax.ejb.SessionContext; import javax.ejb.Stateless; import javax.ejb.TransactionAttribute; import javax.ejb.TransactionAttributeType; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import alctail.bean.ResultBean; import alctail.entity.Itemtable; import alctail.exception.TransactionException; /** * トランザクション管理について(CMT) */ @Stateless //@TransactionAttribute(TransactionAttributeType.REQUIRED) (デフォルトでREQUIRED) public class Study9EJB { @PersistenceContext(unitName="EJBStudy1") private EntityManager em; @Resource private SessionContext ctx; /** 参照系 */ @TransactionAttribute(TransactionAttributeType.SUPPORTS) public ResultBean select() { // レコードを全件取得し、それをテキストに連結して返す ResultBean resultBean = new ResultBean(); String jpql = "SELECT u FROM Itemtable u"; List<Itemtable> resultList = em.createQuery(jpql, Itemtable.class).getResultList(); StringBuilder builder = new StringBuilder(); for (Itemtable record : resultList) { builder.append(record.getId()).append(" : ").append(record.getMsg()).append("\n"); } resultBean.setMsg(builder.toString()); return resultBean; } /** 更新系 */ public ResultBean insert(String msg) { // レコードを1件追加する(キー値はDBで自動採番) Itemtable item = new Itemtable(); item.setMsg(msg); em.persist(item); // 戻り値を作成(戻り値はてきとー) ResultBean resultBean = new ResultBean(); resultBean.setMsg(msg); return resultBean; } /** 更新系(ロールバックフラグをONにするケース) */ public ResultBean insertFalt1(String msg) { Itemtable item = new Itemtable(); item.setMsg(msg); em.persist(item); // トランザクションをロールバックするようにフラグを立てる ctx.setRollbackOnly(); // 戻り値を作成(戻り値はてきとー) ResultBean resultBean = new ResultBean(); resultBean.setMsg(msg); return resultBean; } /** 更新系(例外を投げてロールバックするケース) */ public ResultBean insertFalt2(String msg) throws TransactionException { Itemtable item = new Itemtable(); item.setMsg(msg); em.persist(item); // 例外を投げて、ロールバックさせる throw new TransactionException("例外です!"); } } |
自分でトランザクションを行う場合のサンプルです。
複雑な処理が必要な場合や、更新手順がいろいろとある場合などは、こちらを使用します。
@TransactionManagement(TransactionManagementType.BEAN)を付与することでBMTだと指定することが出来ます。
Study10EJB.java |
package alctail.ejb; import javax.annotation.Resource; import javax.ejb.Stateless; import javax.ejb.TransactionManagement; import javax.ejb.TransactionManagementType; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.transaction.UserTransaction; import alctail.bean.ResultBean; import alctail.entity.Itemtable; /** * トランザクション管理について(BMT) */ @Stateless @TransactionManagement(TransactionManagementType.BEAN) public class Study10EJB { @PersistenceContext(unitName="EJBStudy1") private EntityManager em; @Resource private UserTransaction ut; /** 更新系(正常) */ public ResultBean insert1() throws Exception { try { ut.begin(); Itemtable record = new Itemtable(); record.setMsg("BMTのテスト!"); em.persist(record); ut.commit(); } catch(Exception e) { ut.rollback(); throw e; } ResultBean resultBean = new ResultBean(); resultBean.setMsg("更新OK"); return resultBean; } /** 更新系(異常) */ public ResultBean insert2() throws Exception { try { ut.begin(); Itemtable record = new Itemtable(); record.setMsg("BMTのテスト!"); em.persist(record); ut.rollback(); } catch(Exception e) { ut.rollback(); throw e; } ResultBean resultBean = new ResultBean(); resultBean.setMsg("更新NG"); return resultBean; } } |