前回は Glassfish 4.1.2 のソースコードを元にして EJB のメソッド呼び出しの概要を見ました。
今回は、この流れの中でトランザクションの開始と終了について見ていきます。
前回と同様に今回の説明のために不要な箇所は大幅に省略または改編したものとなりますので注意してください。
コードは Glassfish 4.1.2 系のものです。
- はじめに
- トランザクション開始のアウトライン
- トランザクションの初期化
- トランザクションの開始 BaseContainer.preInvoke()
- トランザクションマネージャ
- トランザクションの begin
- トランザクション終了のアウトライン
- トランザクションの後処理
- トランザクションマネージャ
- トランザクションマネージャの commit
- まとめ
はじめに
EJB 呼び出しは(コンテナ管理トランザクションの場合)、トランザクション属性がデフォルトで REQUIRED になります。
メソッド呼び出し時にトランザクションが開始していなければ開始し、既に開始していればそのトランザクション内で実行する というやつです。
以下のように何もしなければトランザクション属性は REQUIRED として実行されます。
@Stateless public class FooServiceImpl implements FooService { public void hoge(String arg) { } }
明示的にトランザクション属性を指定する場合は、以下のようにアノテーションで指定します。
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) public void hoge(String arg) { }
トランザクション属性は以下のように enum 定義されています。
public enum TransactionAttributeType { MANDATORY, REQUIRED, REQUIRES_NEW, SUPPORTS, NOT_SUPPORTED, NEVER; }
トランザクション開始のアウトライン
大枠は以下のようになります。
トランザクションの初期化
前回見てきたように、EJB のメッドコールは EJBObjectInvocationHandler
が入り口です(ローカルコールの場合は EJBLocalObjectInvocationHandler
)。
package com.sun.ejb.containers; public final class EJBObjectInvocationHandler extends EJBObjectImpl implements InvocationHandler { Object invoke(Class clientInterface, Method method, Object[] args) { EjbInvocation inv = baseContainer.createEjbInvocation(); // ... baseContainer.preInvoke(inv); Object returnValue = baseContainer.intercept(inv); baseContainer.postInvoke(inv); baseContainer.getSecurityManager().resetPolicyContext(); return returnValue; } }
トランザクション属性に応じた処理は、BaseContainer.preInvoke()
で開始処理、BaseContainer.postInvoke()
で終了処理が行われます。
最初に開始処理の中身 BaseContainer.preInvoke()
から見ていきます。
トランザクションの開始 BaseContainer.preInvoke()
BaseContainer の事前処理は以下のようになっています。
package com.sun.ejb.containers; public abstract class BaseContainer implements Container, EjbContainerFacade, JavaEEContainer { public void preInvoke(EjbInvocation inv) { inv.transactionAttribute = inv.invocationInfo.txAttr; inv.container = this; inv.setPreInvokeTxStatus(transactionManager.getStatus()); invocationManager.preInvoke(inv); preInvokeTx(inv); inv.setPreInvokeTxStatus(null); enlistExtendedEntityManagers(ctx); } }
EjbInvocation
に TX 関連の情報を設定して preInvokeTx()
を呼んでいます。
同クラス内の preInvokeTx()
は以下のようになっており、
protected final void preInvokeTx(EjbInvocation inv) throws Exception { if (inv.invocationInfo == null) { inv.invocationInfo = getInvocationInfo(inv); inv.transactionAttribute = inv.invocationInfo.txAttr; } containerTransactionManager.preInvokeTx(inv); }
コンテナの管理するトランザクションマネージャ containerTransactionManager.preInvokeTx()
に処理を委譲しています。
このクラスは具体的には EJBContainerTransactionManager
というクラスです。
トランザクションマネージャ
preInvokeTx()
では、トランザクション属性に応じた switch 文にて各処理を行います。
package com.sun.ejb.containers; public class EJBContainerTransactionManager { final void preInvokeTx(EjbInvocation inv) throws Exception { int txAttr = container.getTxAttr(inv); EJBContextImpl context = (EJBContextImpl)inv.context; Transaction prevTx = context.getTransaction(); 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; case Container.TX_REQUIRES_NEW : if (status != Status.STATUS_NO_TRANSACTION) { inv.clientTx = transactionManager.suspend(); } startNewTx(prevTx, inv); break; // ... default : throw new EJBException("Bad transaction attribute"); } } }
代表的なものだけ抜き出しています。
TX_REQUIRED
の場合、トランザクションが開始していなければ startNewTx()
でトランザクションを開始します。
そうでなければ useClientTx()
でこのEJBの呼び出し元のトランザクションを利用します。
TX_REQUIRES_NEW
の場合には、既存トランザクションをサスペンドして新たなトランザクションを開始することが分かるでしょう。
トランザクションの begin
startNewTx()
は以下のような実装になります。
package com.sun.ejb.containers; public class EJBContainerTransactionManager { private void startNewTx(Transaction prevTx, EjbInvocation inv) throws Exception { transactionManager.begin(); EJBContextImpl context = (EJBContextImpl)inv.context; Transaction tx = transactionManager.getTransaction(); context.setTransaction(tx); transactionManager.enlistComponentResources(); container.afterBegin(context); }
トランザクションマネージャの begin()
してから、Transaction
を取得してコンテキストに設定しています。
ここでの transactionManager は JavaEETransactionManagerSimplified のインスタンスで、その begin()
は以下のようになっています。
package com.sun.enterprise.transaction; @Service @ContractsProvided({TransactionManager.class, JavaEETransactionManager.class}) @Rank(Constants.DEFAULT_IMPLEMENTATION_RANK) public class JavaEETransactionManagerSimplified implements JavaEETransactionManager, PostConstruct { public void begin() throws NotSupportedException, SystemException { begin(getEffectiveTimeout()); } public void begin(int timeout) throws NotSupportedException, SystemException { setDelegate(); initJavaEETransaction(timeout); } }
initJavaEETransaction()
では以下のように JavaEETransactionImpl
を生成して現在のトランザクションに設定しています。
public class JavaEETransactionManagerSimplified implements JavaEETransactionManager, PostConstruct { private JavaEETransactionImpl initJavaEETransaction(int timeout) { JavaEETransactionImpl tx = null; if (timeout > 0) tx = new JavaEETransactionImpl(timeout, this); else tx = new JavaEETransactionImpl(this); setCurrentTransaction(tx); // transactions.set(t); return tx; } }
setCurrentTransaction()
ではスレッドローカルに生成したトランザクションを保存しています。
このように生成したトランザクションは、EJBContainerTransactionManager.startNewTx()
でコンテキストに保存され、コンテキスト経由で利用できるようになります。
トランザクション終了のアウトライン
大枠は以下のようになります。
トランザクションの後処理
再掲になりますが、EJBObjectInvocationHandler
です。
package com.sun.ejb.containers; public final class EJBObjectInvocationHandler extends EJBObjectImpl implements InvocationHandler { Object invoke(Class clientInterface, Method method, Object[] args) { EjbInvocation inv = baseContainer.createEjbInvocation(); // ... baseContainer.preInvoke(inv); Object returnValue = baseContainer.intercept(inv); baseContainer.postInvoke(inv); baseContainer.getSecurityManager().resetPolicyContext(); return returnValue; } }
トランザクションの後処理は BaseContainer.postInvoke()
で行われます。
では BaseContainer.postInvoke()
の中身を見ていきます。
public abstract class BaseContainer implements Container, EjbContainerFacade, JavaEEContainer { public void postInvoke(EjbInvocation inv) { postInvoke(inv, true); } protected void postInvoke(EjbInvocation inv, boolean doTxProcessing) { inv.setDoTxProcessingInPostInvoke(doTxProcessing); delistExtendedEntityManagers(inv.context); if (doTxProcessing) { postInvokeTx(inv); } invocationManager.postInvoke(inv); releaseContext(inv); if (inv.exception != null) { // ... } } protected void postInvokeTx(EjbInvocation inv) throws Exception { containerTransactionManager.postInvokeTx(inv); } }
コンテナの管理するトランザクションマネージャ containerTransactionManager.postInvokeTx()
に処理を委譲しています。
このあたりは開始時と同じで EJBContainerTransactionManager
で処理されることになります。
トランザクションマネージャ
postInvokeTx()
では、トランザクション属性に応じた switch 文にて各処理を行います。
代表的なものだけ以下に示します。このあたりの流れも開始時と同じですね。
public class EJBContainerTransactionManager { protected void postInvokeTx(EjbInvocation inv) throws Exception { Throwable exception = inv.exception; EJBContextImpl context = (EJBContextImpl)inv.context; int status = transactionManager.getStatus(); int txAttr = inv.invocationInfo.txAttr; Throwable newException = inv.exception; switch (txAttr) { case Container.TX_REQUIRED: if (inv.clientTx == null) { newException = completeNewTx(context, exception, status); } else { if (exception != null) { newException = checkExceptionClientTx(context, exception); } } break; case Container.TX_REQUIRES_NEW: newException = completeNewTx(context, exception, status); if (inv.clientTx != null) { transactionManager.resume(inv.clientTx); } break; default: } inv.exception = newException; } }
TX_REQUIRED
の場合、EJB 呼び出し元がトランザクションに参加していなければ completeNewTx()
でトランザクションを完了させます。
TX_REQUIRES_NEW
の場合には、今回のトランザクションを完了し、EJB 呼び出し元がトランザクションに参加していれば resume()
して元のトランザクションに戻る処理となつています。
completeNewTx()
は以下のようになっています。
public class EJBContainerTransactionManager { private Throwable completeNewTx(EJBContextImpl context, Throwable exception, int status) throws Exception { Throwable newException = // ... if ( container.isStatefulSession && (context instanceof SessionContextImpl)) { ((SessionContextImpl) context).setTxCompleting(true); } if (newException != null && container.isSystemUncheckedException(newException)) { destroyBeanAndRollback(context, null); newException = processSystemException(newException); } else { if (status == Status.STATUS_MARKED_ROLLBACK) { transactionManager.rollback(); } else { if (newException != null && isAppExceptionRequiringRollback(newException)) { transactionManager.rollback(); } else { transactionManager.commit(); } } } return newException; }
状態に応じてトランザクションマネージャの commit()
または rollback()
しています。
ここでの transactionManager は JavaEETransactionManagerSimplified のインスタンスで、その commit()
は以下のようになっています。
トランザクションマネージャの commit
基本的には開始時にスレッドローカルに保存した JavaEETransaction
を取得して commit()
となります。
@Service @ContractsProvided({TransactionManager.class, JavaEETransactionManager.class}) @Rank(Constants.DEFAULT_IMPLEMENTATION_RANK) public class JavaEETransactionManagerSimplified implements JavaEETransactionManager, PostConstruct { public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, SecurityException, IllegalStateException, SystemException { try { JavaEETransaction tx = transactions.get(); if (tx != null && tx.isLocalTx()) { tx.commit(); } else { try { getDelegate().commitDistributedTransaction(); } finally { if (tx != null) { ((JavaEETransactionImpl)tx).onTxCompletion(true); } } } } finally { setCurrentTransaction(null); delegates.set(null); } } }
finally 句において、setCurrentTransaction()
でスレッドローカルに保存したトランザクションをクリアしています。
また、開始時にコンテキストに設定したトランザクションは、BaseContainer.postInvoke()
から以下が呼ばれてクリアされます。
package com.sun.ejb.containers; public class StatelessSessionContainer extends BaseContainer { public void releaseContext(EjbInvocation inv) { SessionContextImpl sc = (SessionContextImpl)inv.context; sc.setState(EJBContextImpl.BeanState.POOLED); sc.setTransaction(null); sc.touch(); pool.returnObject(sc); } }
まとめ
駆け足でしたが、
- EJB のトランザクションは、
BaseContainer.preInvoke()
の中で開始し、BaseContainer.postInvoke()
の中で終了する - トランザクション開始時には
JavaEETransaction
が生成されてSessionContext
に設定される - トランザクション終了時には
SessionContext
に設定されたJavaEETransaction
がクリアされる - トランザクション属性に応じた処理は
EJBContainerTransactionManager
で扱われる
次回はトランザクションの suspend と resume についてもう少し詳細を説明しようと思います。