Springフレームワークにおけるトランザクションの実装メカニズムとソースコード分析

序論

前の記事「Springトランザクション詳細解説」では、Springにおけるトランザクションの理論的知識と実践的な内容を詳しく整理しました。本稿では現象の背後にある本質に迫り、Springトランザクションの内部実装原理と関連するソースコードを深く掘り下げて分析します。

1. トランザクション関連コンポーネント

1.1 トランザクション状態TransactionStatus

TransactionStatusはトランザクションの状態情報を表すインターフェースであり、SavepointManagerおよびFlushableインターフェースを継承しています。そのソースコードは以下の通りです:

 1 public interface TransactionStatus extends SavepointManager, Flushable {
 2 
 3         /**
 4          * 新規トランザクションであるかを判定
 5          */
 6         boolean isFreshTransaction();
 7 
 8         /**
 9          * セーブポイントを持っているかを判定
10          */
11         boolean containsSavepoint();
12 
13         /**
14          * ロールバック専用に設定
15          * ロールバック専用フラグがtrueの場合、例外発生時は必ずロールバックされる
16          */
17         void markRollbackOnly();
18 
19         /**
20          * ロールバック専用としてマークされているかを判定
21          */
22         boolean isRollbackOnly();
23 
24         /**
25          * フラッシュ処理
26          */
27         @Override
28         void flush();
29 
30         /**
31          * トランザクション完了状態を判定
32          */
33         boolean isFinished();
34 
35     }

TransactionStatusはSavepointManagerインターフェースを継承しているため、実装クラスはSavepointManagerが定義するメソッドも実装する必要があります:

 1 public interface SavepointManager {
 2         /**
 3          * セーブポイントを作成
 4          */
 5         Object generateSavepoint() throws TransactionException;
 6 
 7         /**
 8          * 指定セーブポイントまでロールバック
 9          */
10         void revertToSavepoint(Object savepoint) throws TransactionException;
11 
12         /**
13          * セーブポイントを解放
14          */
15         void disposeSavepoint(Object savepoint) throws TransactionException;
16     }

TransactionStatusのデフォルト実装クラスはDefaultTransactionStatusで、コンストラクタとプロパティは以下の通り:

 1 /** 現在のトランザクションオブジェクト */
 2         private final Object transaction;
 3 
 4         /** 新規トランザクションであるか */
 5         private final boolean freshTransaction;
 6 
 7         /** 同期が必要か */
 8         private final boolean requiresSynchronization;
 9 
10         /** 読み取り専用トランザクションか */
11         private final boolean readOnlyMode;
12 
13         private final boolean enableDebug;
14 
15         private final Object suspendedResources;
16 
17 
18         /**
19          * コンストラクタで自身の属性を設定
20          */
21         public DefaultTransactionStatus(
22                 Object transaction, boolean freshTransaction, boolean requiresSynchronization,
23                 boolean readOnlyMode, boolean enableDebug, Object suspendedResources) {
24 
25             this.transaction = transaction;
26             this.freshTransaction = freshTransaction;
27             this.requiresSynchronization = requiresSynchronization;
28             this.readOnlyMode = readOnlyMode;
29             this.enableDebug = enableDebug;
30             this.suspendedResources = suspendedResources;
31         }

TransactionStatusはトランザクションへの参照を持ち、現在のトランザクション状態属性を含んでいます。そのため、各トランザクション作成時にTransactionStatusオブジェクトにカプセル化されます。トランザクションの管理はTransactionManagerを通じて行われます。

1.2 トランザクションマネージャーTransactionManager

トランザクションマネージャーはトランザクションの管理を担当し、SpringではPlatformTransactionManagerインターフェースで定義されています:

 1 public interface PlatformTransactionManager {
 2         /**
 3          * トランザクション状態を取得
 4          */
 5         TransactionStatus acquireTransaction(TransactionDefinition definition) throws TransactionException;
 6 
 7         /**
 8          * トランザクションをコミット
 9          */
10         void performCommit(TransactionStatus status) throws TransactionException;
11 
12         /**
13          * トランザクションをロールバック
14          */
15         void executeRollback(TransactionStatus status) throws TransactionException;
16     }

このインターフェースはトランザクション取得、コミット、ロールバックの3つのメソッドのみ定義しており、これらの実装はAbstractPlatformTransactionManager抽象クラスで提供されます。

1.2.1 トランザクション状態取得メソッドacquireTransactionのソースコード解析

 1 /**
 2      * 現在のトランザクション状態を取得
 3      * */
 4     @Override
 5     public final TransactionStatus acquireTransaction(TransactionDefinition definition) throws TransactionException {
 6 
 7         /**
 8          * 1.サブクラスメソッドを呼び出してトランザクションを取得
 9          * */
10         Object transaction = retrieveTransaction();
11 
12         if (definition == null) {
13             /** パラメータが渡されない場合、デフォルトトランザクション定義を作成 */
14             definition = new DefaultTransactionDefinition();
15         }
16 
17         /**
18          * 2.現在のスレッドに既存トランザクションがあるかを確認
19          * */
20         if (hasActiveTransaction(transaction)) {
21             return handleActiveTransaction(definition, transaction, debugEnabled);
22         }
23 
24         /******** 以下は現在のスレッドにトランザクションが存在しない場合のロジック *******/
25 
26         /**
27          * 3.現在のトランザクションがタイムアウトしていないかを判定
28          * */
29         if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
30             throw new InvalidTimeoutException("無効なトランザクションタイムアウト", definition.getTimeout());
31         }
32 
33         /**
34          *
35          * 4.トランザクション伝播メカニズムを判定、MANDATORY型かつスレッドにトランザクションがない場合は例外処理
36          */
37         if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
38             throw new IllegalTransactionStateException(
39                     "伝播モード'mandatory'でマークされたトランザクションに既存トランザクションが見つかりません");
40         }
41         else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
42                 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
43                 definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
44             SuspendedResourcesHolder suspendedResources = pauseTransaction(null);
45             try {
46                 boolean requiresSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
47                 /**
48                  * 5.新規トランザクションを作成
49                  * */
50                 DefaultTransactionStatus status = buildTransactionStatus(
51                         definition, transaction, true, requiresSynchronization, debugEnabled, suspendedResources);
52                 /**
53                  * 6.トランザクション開始
54                  * */
55                 initializeTransaction(transaction, definition);
56                 /**
57                  * 7.トランザクションの初期化と同期
58                  * */
59                 setupSynchronization(status, definition);
60                 return status;
61             }
62             catch (RuntimeException ex) {
63                 resumeTransaction(null, suspendedResources);
64                 throw ex;
65             }
66             catch (Error err) {
67                 resumeTransaction(null, suspendedResources);
68                 throw err;
69             }
70         }
71         else {
72             if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) {
73                 logger.warn("カスタム分離レベルが指定されたが実際のトランザクションは開始されていません; " +
74                         "分離レベルは実質的に無視されます: " + definition);
75             }
76             boolean requiresSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
77             return prepareTransactionStatus(definition, null, true, requiresSynchronization, debugEnabled, null);
78         }
79     }

1.2.2 コミットメソッドperformCommitのソースコード解析

 1 @Override
 2     public final void performCommit(TransactionStatus status) throws TransactionException {
 3         /** 1.トランザクションが完了している場合、例外をスロー */
 4         if (status.isFinished()) {
 5             throw new IllegalTransactionStateException(
 6                     "トランザクションは既に完了しています - 一度のトランザクションで複数回のコミットやロールバックを呼び出さないでください");
 7         }
 8 
 9         /**2. トランザクションがロールバックを必要とする場合、processRollbackを実行してロールバック */
10         DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
11         if (defStatus.isLocallyRollbackOnly()) {
12             if (defStatus.isDebug()) {
13                 logger.debug("トランザクショナルコードがロールバックを要求しました");
14             }
15             processRollback(defStatus);
16             return;
17         }
18         if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGloballyRollbackOnly()) {
19             if (defStatus.isDebug()) {
20                 logger.debug("グローバルトランザクションがロールバック専用にマークされていますが、トランザクショナルコードがコミットを要求しました");
21             }
22             /** 3. トランザクションがロールバックを必要とする場合、ロールバック操作を実行 */
23             processRollback(defStatus);
24             if (status.isFreshTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
25                 throw new UnexpectedRollbackException(
26                         "トランザクションはロールバック専用にマークされているためロールバックされました");
27             }
28             return;
29         }
30         /** 4.ロールバック不要の場合、最終的にコミットロジックを実行 */
31         processCommit(defStatus);
32     }

最終的に実行されるコミットロジックはprocessCommitメソッドで、そのソースコードは以下の通り:

 1 private void processCommit(DefaultTransactionStatus status) throws TransactionException {
 2         try {
 3             boolean beforeCompletionInvoked = false;
 4             try {
 5                 prepareForCommit(status);
 6                 triggerBeforeCommit(status);
 7                 triggerBeforeCompletion(status);
 8                 beforeCompletionInvoked = true;
 9                 boolean globalRollbackOnly = false;
10                 if (status.isFreshTransaction() || isFailEarlyOnGlobalRollbackOnly()) {
11                     globalRollbackOnly = status.isGloballyRollbackOnly();
12                 }
13                 /**
14                  * 1. トランザクションにセーブポイントが設定されている場合、すべてのセーブポイントを解放
15                  * */
16                 if (status.containsSavepoint()) {
17                     if (status.isDebug()) {
18                         logger.debug("トランザクションセーブポイントを解放中");
19                     }
20                     /** トランザクションセーブポイントを解放 */
21                     status.releaseHeldSavepoint();
22                 }
23                 else if (status.isFreshTransaction()) {
24                     if (status.isDebug()) {
25                         logger.debug("トランザクションコミットを開始");
26                     }
27                     /** 2.トランザクションをコミット */
28                 performDatabaseCommit(status);
29                 }
30                 // グローバルロールバック専用マーカーがあるにもかかわらず、
31                 // コミットから対応する例外を受け取らなかった場合、UnexpectedRollbackExceptionをスロー
32                 if (globalRollbackOnly) {
33                     throw new UnexpectedRollbackException(
34                             "トランザクションはロールバック専用にマークされているためサイレントロールバックされました");
35                 }
36             }
37             catch (UnexpectedRollbackException ex) {
38                 // doCommitによってのみ引き起こされる可能性あり
39                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
40                 throw ex;
41             }
42             catch (TransactionException ex) {
43                 // doCommitによってのみ引き起こされる可能性あり
44                 if (isRollbackOnCommitFailure()) {
45                     performRollbackOnCommitException(status, ex);
46                 }
47                 else {
48                     triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
49                 }
50                 throw ex;
51             }
52             catch (RuntimeException ex) {
53                 if (!beforeCompletionInvoked) {
54                     triggerBeforeCompletion(status);
55                 }
56                 performRollbackOnCommitException(status, ex);
57                 throw ex;
58             }
59             catch (Error err) {
60                 if (!beforeCompletionInvoked) {
61                     triggerBeforeCompletion(status);
62                 }
63                 performRollbackOnCommitException(status, err);
64                 throw err;
65             }
66 
67             // afterCommitコールバックをトリガーし、そこでの例外は呼び出し元に伝播されるが、
68             // トランザクションは依然としてコミット済みとみなされる
69             try {
70                 triggerAfterCommit(status);
71             }
72             finally {
73                 triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
74             }
75 
76         }
77         finally {
78             cleanupAfterCompletion(status);
79         }
80     }

最終的にperformDatabaseCommitメソッドが実行されますが、これは抽象メソッドであり、実際の実装はAbstractPlatformTransactionManagerのサブクラスで行われます。一般的にはDataSourceTransactionManagerクラスが使用されます。

 1 @Override
 2     protected void performDatabaseCommit(DefaultTransactionStatus status) {
 3         DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
 4         /**
 5          * 1.トランザクションのデータベース接続を取得
 6          * */
 7         Connection con = txObject.getConnectionHolder().getConnection();
 8         if (status.isDebug()) {
 9             logger.debug("Connection [" + con + "] でJDBCトランザクションをコミット中");
10         }
11         try {
12             /**
13              * 2.データベース接続のコミットメソッドを実行してトランザクションをコミット
14              * */
15             con.commit();
16         }
17         catch (SQLException ex) {
18             throw new TransactionSystemException("JDBCトランザクションをコミットできませんでした", ex);
19         }
20     }

1.2.3 ロールバックメソッドexecuteRollbackのソースコード解析

 1 @Override
 2     public final void executeRollback(TransactionStatus status) throws TransactionException {
 3         if (status.isFinished()) {
 4             throw new IllegalTransactionStateException(
 5                     "トランザクションは既に完了しています - 一度のトランザクションで複数回のコミットやロールバックを呼び出さないでください");
 6         }
 7 
 8         DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
 9    /** processRollbackメソッドを実行してトランザクションをロールバック */
10         processRollback(defStatus);
11     }

processRollbackメソッドはprocessCommitメソッドと同様のフローを持ち、最終的にdoRollbackメソッドを呼び出し、JDBCのデータベース接続のrollbackメソッドを使用してトランザクションのロールバック操作を実行します。

2. プログラムによるトランザクションのソースコード解析

プログラムによるトランザクションロジックは比較的明確です。なぜならトランザクションがビジネスコード内で明示的に実行されるため、理解しやすいです。最も一般的なプログラミングトランザクションはトランザクションテンプレート、つまりTransactionTemplateクラスを使用します。TransactionTemplateはTransactionDefinitionインターフェースを実装し、主にトランザクションのグローバル設定を保持する役割を果たします。これにより、トランザクションマネージャーのgetTransaction(TransactionDefinition definition)メソッドで取得されるトランザクション属性はすべて同じになります。使用方法は以下の通り:

 1 @Override
 2     public User addAndGet(User user, Long userId) {
 3         return transactionTemplate.execute(new TransactionCallback<User>() {
 4             @Override
 5             public User doInTransaction(TransactionStatus status) {
 6                 userMapper.addUser(user);
 7                 return userMapper.getUserById(userId);
 8             }
 9         });
10     }

したがって、プログラミングトランザクションの実装は主にTransactionTemplateのexecuteメソッドを通じて行われるため、直接executeメソッドから解析を開始してトランザクションの実装方法を分析できます。

2.1 TransactionTemplateのexecuteメソッドソースコード解析

 1 /**
 2      * トランザクション内のビジネスメソッドを実行
 3      * */
 4     public <T> T execute(TransactionCallback<T> action) throws TransactionException {
 5         if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
 6             return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
 7         }
 8         else {
 9             /**
10              * 1.現在のトランザクション状態を取得、transactionManagerは設定されたTransactionManagerインスタンス
11              * */
12             TransactionStatus status = this.transactionManager.acquireTransaction(this);
13             T result;
14             try {
15                 /**
16                  * 2.具体的なビジネスロジックを実行
17                  * */
18                 result = action.doInTransaction(status);
19             }
20             catch (RuntimeException ex) {
21                 /** ビジネスがRuntimeException例外をスローした場合ロールバックを実行 */
22                 rollbackOnException(status, ex);
23                 throw ex;
24             }
25             catch (Error err) {
26                 /** ビジネスがError例外をスローした場合ロールバックを実行 */
27                 rollbackOnException(status, err);
28                 throw err;
29             }
30             catch (Throwable ex) {
31                 /**
32                  * ビジネスがThrowable例外をスローした場合ロールバックを実行
33                  * */
34                 rollbackOnException(status, ex);
35                 throw new UndeclaredThrowableException(ex, "TransactionCallbackが宣言されていないチェック例外をスローしました");
36             }
37             /**
38              * 例外がスローされない場合、トランザクションをコミット
39              * */
40             this.transactionManager.performCommit(status);
41             return result;
42         }
43     }
44 
45     /**
46      * 例外時のロールバック
47      * */
48     private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
49         try {
50             /**
51              * トランザクションマネージャーのロールバックメソッドを実行してロールバック
52              * */
53             this.transactionManager.executeRollback(status);
54         }
55         catch (TransactionSystemException ex2) {
56             logger.error("アプリケーション例外がロールバック例外によって上書きされました", ex);
57             ex2.initApplicationException(ex);
58             throw ex2;
59         }
50         catch (RuntimeException ex2) {
51             logger.error("アプリケーション例外がロールバック例外によって上書きされました", ex);
52             throw ex2;
53         }
54         catch (Error err) {
55             logger.error("アプリケーション例外がロールバックエラーによって上書きされました", ex);
56             throw err;
57         }
58     }

プログラミング方式のトランザクションフローは比較的明確で、主にトランザクションマネージャーtransactionManagerのメソッド呼び出しによって実現されます。まずgetTransactionメソッドを呼び出して現在のトランザクションを取得し、次にビジネスロジックを実行してerrorと実行時例外をキャッチします。例外が発生した場合、トランザクションマネージャーのロールバックメソッドを実行してトランザクションをロールバックします。

例外が発生しなかった場合、トランザクションマネージャーのコミットメソッドを実行してトランザクションをコミットします。

3. 宣言的トランザクションのソースコード解析

宣言的トランザクションはSpringのAOPを介して実装されます。使用方法は以下の通り:

1     @Transactional
2     public User addAndGet(User user, Long userId) {
3         userMapper.addUser(user);
4         return userMapper.getUserById(userId);
5     }

AOPの実装フローに精通している方であれば、宣言的トランザクションの実装が@Transactionで修飾されたクラスまたはメソッドをインターセプトし、アドバイスの作成を通じてトランザクションロジックをビジネスコードに織り込むことであると推測できるでしょう。

@Transactionalアノテーションの解析とトランザクションロジックの織り込みは、MethodInterceptorの実装クラスTransactionInterceptorで行われます。したがって、@Transactionalアノテーションで修飾されたメソッドがビジネスロジックを実行する際、常にTransactionInterceptorのinvokeメソッドが実行されます:

 1 @Override
 2     public Object invoke(final MethodInvocation invocation) throws Throwable {
 3         /**
 4          * 1.インターセプターのターゲットクラスを取得
 5          * */
 6         Class targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
 7 
 8         /**
 9          * トランザクションをビジネスロジックに織り込む
10          * */
11         return invokeWithinTransaction(invocation.getMethod(), targetClass, new InvocationCallback() {
12             @Override
13             public Object proceedWithInvocation() throws Throwable {
14                 return invocation.proceed();
15             }
16         });
17     }
 1 protected Object invokeWithinTransaction(Method method, Class targetClass, final InvocationCallback invocation)
 2             throws Throwable {
 3 
 4         /** 1.トランザクション設定属性を取得 */
 5         final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
 6         /** 2.トランザクションマネージャーを取得 */
 7         final PlatformTransactionManager tm = determineTransactionManager(txAttr);
 8         final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
 9 
10         if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
11             /** 3.TranscationInfoオブジェクトを作成、トランザクション開始に相当 */
12             TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
13             Object retVal = null;
14             try {
15                 /** 4.アドバイスされたメソッド(ビジネスロジック)を実行 */
16                 retVal = invocation.proceedWithInvocation();
17             }
18             catch (Throwable ex) {
19                 /** 5.例外後にトランザクションをロールバック */
20                 completeTransactionAfterThrowing(txInfo, ex);
21                 throw ex;
22             }
23             finally {
24                 /** 6.finallyブロックでトランザクション関連情報をクリーンアップ */
25                 cleanupTransactionInfo(txInfo);
26             }
27             /** 7.ビジネスロジック実行完了後にトランザクションをコミット */
28             commitTransactionAfterReturning(txInfo);
29             return retVal;
30         }
31     }

宣言的トランザクションの実装手順をまとめると以下の通り:

  1. トランザクションをAOPアドバイスとしてカプセル化し、@Transactionalアノテーションで修飾されたメソッドまたはクラスをインターセプトする。または<tx>タグで設定されたトランザクションを処理する。アドバイスをメソッドインターセプターTransactionInterceptorとしてカプセル化する
  2. トランザクションを追加したメソッド実行時に、アドバイスインターセプターTransactionInterceptorのinvokeメソッドを実行する
  3. 構成からトランザクションの構成(伝播メカニズム、分離メカニズム、タイムアウト時間など)を取得する
  4. SpringコンテナからPlatformTransactionManagerのBeanを取得する
  5. トランザクションマネージャーのgetTransactionメソッドを呼び出して新しいトランザクションを開始する
  6. インターセプトされたビジネスロジックを実行し、try/catchで例外をキャッチする
  7. 例外をキャッチした場合、トランザクションマネージャーのrollbackメソッドを実行してトランザクションをロールバックする
  8. 例外をキャッチしなかった場合、トランザクションマネージャーのcommitメソッドを実行してトランザクションをコミットする

ヒント:

プログラミングトランザクションと宣言的トランザクションのどちらも実際にはトランザクションマネージャーを通じて実装され、まずトランザクションマネージャーを使用して現在のスレッドのトランザクションを取得し、ビジネスロジックを実行します。例外がロールバックを必要とする場合、トランザクションマネージャーのロールバックメソッドを実行してロールバックし、ビジネス実行が成功した場合、トランザクションマネージャーのコミットメソッドを実行してトランザクションをコミットします。

トランザクションマネージャーのトランザクション取得、ロールバック、コミットはすべて下位のJDBCデータベース接続に対応するメソッドを呼び出すことで実現されますが、データベース接続のトランザクション取得、コミット、ロールバックメソッドの上位にラッパーを提供することで、ビジネスコードがトランザクションを使用する際に下位JDBC APIを意識する必要がありません。

タグ: spring-framework transaction-management AOP JDBC source-code-analysis

6月20日 23:21 投稿