メンチカツには醤油でしょ!!

ITエンジニア徒然 (AWS/Java/JavaScript/Google Spreadsheets/Jenkins/Mac/外部コミュニティ・勉強会レポ)

EJB3でネストされたStateless Beanのトランザクションの伝搬がされなかった話

イージーミスの話です

EJB 3 + JPAっていい

@Statelessアノテーションを1つ付すことで、EJBとみなすことができトランザクションも自動で管理されるという手軽さ/良さがウリですね。

また、EJBからEJBを呼び出した場合(ex. EJB_A⇒EJB_B)、デフォルトで同一トランザクションとなり、また、EJB_Bだけを呼び出した場合でもこれはこれで1つのトランザクションとなるよう管理されます。
beginとかcommitとかrollbackを明示的に呼び出さなくって良いって、いいもんだ。

ロールバックされない

と、上記のJava EE標準挙動を信じていたのですが、
EJB_A⇒EJB_B呼び出し時にEJB_BでExceptionが発生した場合に、ロールバックされずEJB_Bがコミットされてしまうということがありました。

コード

ejb-jar.xml

<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="3.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/ejb-jar_3_2.xsd">
  <interceptors>
    <interceptor>
      <interceptor-class>com.ryoichi0102.myapplication.EJBExceptionHandlingInterceptor</interceptor-class>
    </interceptor>
  </interceptors>   <assembly-descriptor>
    <interceptor-binding>
      <ejb-name>*</ejb-name>
      <interceptor-class>com.ryoichi0102.myapplication.EJBExceptionHandlingInterceptor</interceptor-class>
    </interceptor-binding>
  </assembly-descriptor>
</ejb-jar>

ejb-jar.xml で指定しているEJBExceptionHandlingInterceptor

@Interceptor
@Dependent
@EJBExceptionHandling
public class EJBExceptionHandlingInterceptor implements Serializable {
  @AroundInvoke
  public Object invoke(InvocationContext ic) throws Exception {
    try {
      return ic.proceed();
    } catch (Throwable t) {
     throw handle(t);
    }
  }
}

EJB_A.java

@Stateless
@EJBExceptionHandling
public class EJB_A {
  public void execute() {
    // 処理
  }
}

解決策

アプリケーションで独自に作成したExceptionクラスの@ApplicationExceptionアノテーションにrollback=trueを足したらうまくいきました。

@ApplicationException(rollback=true)
public class MyApplicationException extends RuntimeException {

考察

・RuntimeExceptionをextendsしてるのがダメだった?
・デフォルトrollback=falseだけどいつ使うのこれ?
・そもそも@ApplicationException付けない方が良い?
・EJB側からEJBTransactionRolledbackExceptionが投げられてEJBExceptionHandlingInterceptorでは取れない(EJBを抜ける最後の最後に投げられてるっぽい)EJBのハンドリングで何とかできないものか。

などモヤモヤ。

ちなみにEJBには

CMT(Container Managed Transaction)とBMT(Bean Managed Transaction)があり
CMTだとコンテナが自動でトランザクション管理してくれますが
自分でcommit/rollbackをするようなBMTも設定できます。

また、トランザクションタイプもRequired, RequiresNewほかが指定でき、任意のタイミングでトランザクションを新規に開始することもできます。

今回はCMT, Requiredです。

参考サイト

stackoverflow.com

Oracle公式ドキュメント

51.3 Container-Managed Transactions - Java Platform, Enterprise Edition: The Java EE Tutorial (Release 7)