前回はEJBにおけるトランザクションの開始と終了の流れを見てきました。
今回は、トランザクション属性による動作を少しだけみてみましょう。
はじめに
TransactionAttributeType.REQUIRED
のEJBメソッドから、TransactionAttributeType.REQUIRES_NEW
の別EJBのメソッドを呼び出すケースを例として見ていきます。
最初の EJB は以下のような感じです。
@Stateless public class Service1Impl implements Service1 { @EJB private Service2 service2; @Override public void exec() { service2.exec(); } }
このメソッド内で service2.exec()
と別 EJB のメソッドを呼び出します。
@Stateless public class ServiceBImpl implements ServiceB { @Override @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void exec() { // ... } }
呼び出し先は TransactionAttributeType.REQUIRES_NEW
でアノテートされており、こちらが別トランザクションで実行される。
といった例です。
最初のトランザクション
Service1
側ではデフォルトで TransactionAttributeType.REQUIRED
として処理されます。
この場合、前回見てきたものと同じく、EJBContainerTransactionManager.preInvokeTx()
でトランザクション属性に応じた処理が行われます。
package com.sun.ejb.containers; public class EJBContainerTransactionManager { final void preInvokeTx(EjbInvocation inv) throws Exception { EJBContextImpl context = (EJBContextImpl)inv.context; Transaction prevTx = context.getTransaction(); int txAttr = container.getTxAttr(inv); switch (txAttr) { case Container.TX_REQUIRED: if (status == Status.STATUS_NO_TRANSACTION) { inv.clientTx = null; startNewTx(prevTx, inv); } else { inv.clientTx = transactionManager.getTransaction(); useClientTx(prevTx, inv); } break; // ... } } }
現在のトランザクションは無いので、startNewTx()
で新しいトランザクションが作成されます。
新しいトランザクションは、前回見た通り、
JavaEETransactionImpl
のインスタンスを作成JavaEETransactionManagerSimplified
のスレッドローカルにトランザクションを保存EjbInvocation
が保持するコンテキストにトランザクションを保存
という流れです。
次のトランザクション
TransactionAttributeType.REQUIRED
の EJB メソッドから、TransactionAttributeType.REQUIRES_NEW
の 別EJBメソッドを呼び出した場合も、同じように EJBContainerTransactionManager.preInvokeTx()
で以下のように処理されます。
package com.sun.ejb.containers; public class EJBContainerTransactionManager { final void preInvokeTx(EjbInvocation inv) throws Exception { // ... switch (txAttr) { case Container.TX_REQUIRES_NEW: if (status != Status.STATUS_NO_TRANSACTION) { inv.clientTx = transactionManager.suspend(); } startNewTx(prevTx, inv); break; // ... } } }
トランザクションがあれば、transactionManager.suspend() でトランザクションをサスペンドし、
EjbInvocation の clientTx、つまり今回のEJB呼び出しのクライアントのトランザクションとして保存しています。
トランザクションのサスペンド
transactionManager.suspend()
は何をしているのでしょうか?
JavaEETransactionManagerSimplified.suspend()
で以下のように処理されています。
public class JavaEETransactionManagerSimplified implements JavaEETransactionManager, PostConstruct { public Transaction suspend() throws SystemException { return getDelegate().suspend(transactions.get()); } }
委譲しているだけですね。
委譲先は、JTS の場合は JavaEETransactionManagerJTSDelegate
となります。
@Service public class JavaEETransactionManagerJTSDelegate implements JavaEETransactionManagerDelegate, PostConstruct { private JavaEETransactionManager javaEETM; public Transaction suspend(JavaEETransaction tx) throws SystemException { if (!tx.isLocalTx()) suspendXA(); javaEETM.setCurrentTransaction(null); return tx; } }
javaEETM.setCurrentTransaction(null)
で現在のトランザクションを空更新しています(JavaEETransactionManager
のスレッドローカルを空更新しています)。
つまり、サスペンドとは現在のトランザクションを空に設定する。ということになります。
その後、前述の EJBContainerTransactionManager.preInvokeTx()
の中で EJBContainerTransactionManager.startNewTx()
として新しいトランザクションが作成して、作成したトランザクションを現在のトランザクションとして設定して利用します。
REQUIRES_NEW トランザクションの終了
TransactionAttributeType.REQUIRES_NEW
でアノテートされたメソッドを抜ける時の処理はどのようになるでしょうか。
EJBContainerTransactionManager.postInvokeTx()
で処理されます。
public class EJBContainerTransactionManager { protected void postInvokeTx(EjbInvocation inv) throws Exception { EJBContextImpl context = (EJBContextImpl)inv.context; int status = transactionManager.getStatus(); int txAttr = inv.invocationInfo.txAttr; switch (txAttr) { case Container.TX_REQUIRES_NEW: newException = completeNewTx(context, exception, status); if (inv.clientTx != null) { transactionManager.resume(inv.clientTx); } break; } } }
開始時と同じような流れですね。
completeNewTx()
でトランザクションを終了し、開始時に設定した呼び出し元のトランザクション(inv.clientTx
) でレジュームしています。
具体的には JavaEETransactionManagerSimplified.resume()
で以下のような処理が行われます。
public class JavaEETransactionManagerSimplified implements JavaEETransactionManager, PostConstruct { public void resume(Transaction tobj) throws InvalidTransactionException, IllegalStateException, SystemException { JavaEETransaction tx = transactions.get(); if ( tobj instanceof JavaEETransactionImpl ) { JavaEETransactionImpl javaEETx = (JavaEETransactionImpl)tobj; if ( !javaEETx.isLocalTx() ) getDelegate().resume(javaEETx.getJTSTx()); setCurrentTransaction(javaEETx); } else { getDelegate().resume(tobj); } } }
setCurrentTransaction()
で呼び出し元のトランザクションを戻しているだけです。
REQUIRES_NEW とは
結局のところ、現在のトランザクションをサスペンドで空に更新し、新しいトランザクションを開始
終わったら現在のトランザクションを元のトランザクションで更新
というだけの話になります。 たまにEJBのメソッドがネストされているのでトランザクションもネストされていると勘違いされる人もいますが、単に現在のトランザクションは横に置いておいて新しいトランザクションを開始しているだけです。
その他のトランザクション属性
今まで見てきたように、トランザクション属性別の挙動は EJBContainerTransactionManager.preInvokeTx()
を見れば一目瞭然です。
MANDATORY
呼出し元で開始されているトランザクションで実行。
トランザクションが開始されていなければ例外。
case Container.TX_MANDATORY: if ( isNullTx || status == Status.STATUS_NO_TRANSACTION ) { throw new TransactionRequiredLocalException(); } useClientTx(prevTx, inv); break;
REQUIRED
トランザクションが開始していない場合は新しいトランザクションを開始
トランザクションが開始していればそのトランザクションで実行。
case Container.TX_REQUIRED: if ( isNullTx ) { throw new TransactionRequiredLocalException(); } if ( status == Status.STATUS_NO_TRANSACTION ) { inv.clientTx = null; startNewTx(prevTx, inv); } else { // There is a client Tx inv.clientTx = transactionManager.getTransaction(); useClientTx(prevTx, inv); } break;
REQUIRES_NEW
トランザクションが開始していればサスペンド
新しいトランザクションを開始
case Container.TX_REQUIRES_NEW: if ( status != Status.STATUS_NO_TRANSACTION ) { inv.clientTx = transactionManager.suspend(); } startNewTx(prevTx, inv); break;
SUPPORTS
トランザクションが開始していればそのトランザクションで実行
トランザクションが開始されていなければトランザクション無しで実行
case Container.TX_SUPPORTS: if ( isNullTx ) { throw new TransactionRequiredLocalException(); } if ( status != Status.STATUS_NO_TRANSACTION ) { useClientTx(prevTx, inv); } else { // we need to invoke the EJB with no Tx. container.checkUnfinishedTx(prevTx, inv); container.preInvokeNoTx(inv); } break;
NOT_SUPPORTED
トランザクションが開始されている場合はサスペンド
トランザクションが開始していなければそのまま実行
case Container.TX_NOT_SUPPORTED: if ( status != Status.STATUS_NO_TRANSACTION ) { inv.clientTx = transactionManager.suspend(); } container.checkUnfinishedTx(prevTx, inv); container.preInvokeNoTx(inv); break;
NEVER
トランザクションが開始されている場合は例外
トランザクションが開始していなければそのまま実行
case Container.TX_NEVER: if ( isNullTx || status != Status.STATUS_NO_TRANSACTION ) { throw new EJBException("EJB cannot be invoked in global transaction"); } else { // we need to invoke the EJB with no Tx. container.checkUnfinishedTx(prevTx, inv); container.preInvokeNoTx(inv); } break;
日本語で説明されるより、コードで見る方が分かりやすいですね。