web-dev-qa-db-ja.com

Springの@Transactional属性はプライベートメソッドで機能しますか?

Spring Beanのプライベートメソッドに @ Transactional -annotationがある場合、注釈は効果がありますか?

@Transactional注釈がパブリックメソッドにある場合、それは機能し、トランザクションを開きます。

public class Bean {
  public void doStuff() {
     doPrivateStuff();
  }
  @Transactional
  private void doPrivateStuff() {

  }
}

...

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();
178
Juha Syrjälä

質問はプライベートでもパブリックでもありません。質問は、どのように呼び出され、どのAOP実装を使用するかです。

(デフォルト)Spring Proxy AOPを使用する場合、Springが提供するすべてのAOP機能(@Transationalなど)は、呼び出しがプロキシを通過する場合にのみ考慮されます。 -これは通常、注釈付きメソッドがanotherBeanから呼び出される場合です。

これには2つの意味があります。

  • プライベートメソッドは別のBeanから呼び出すことはできないため(例外はリフレクションです)、@Transactionalアノテーションは考慮されません。
  • メソッドがパブリックであるが、同じBeanから呼び出された場合、それも考慮されません(このステートメントは、(デフォルト)Spring Proxy AOPが使用されている場合のみ正しいです)。

@See Spring Reference:Chapter 9.6 9.6 Proxying mechanism

私見では、Spring Proxiesの代わりにaspectJモードを使用する必要があります。これは問題を克服します。また、AspectJ Transactional Aspectsはプライベートメソッドにも組み込まれています(Spring 3.0で確認済み)。

141
Ralph

あなたの質問に対する答えは「いいえ」です。@Transactionalは、プライベートメソッドに注釈を付けるために使用しても効果がありません。プロキシジェネレーターはそれらを無視します。

これは Spring Manual 10.5.6 で文書化されています:

メソッドの可視性と@Transactional

プロキシを使用する場合は、@Transactional注釈をパブリック可視性を持つメソッドにのみ適用する必要があります。 @Transactional注釈を使用して、保護されたメソッド、プライベートメソッド、またはパッケージから見えるメソッドに注釈を付けた場合、エラーは発生しませんが、注釈付きメソッドは構成済みのトランザクション設定を示しません。非パブリックメソッドに注釈を付ける必要がある場合は、AspectJ(以下を参照)の使用を検討してください。

210
skaffman

デフォルトでは、@Transactional属性は、applicationContextから取得した参照で注釈付きメソッドを呼び出す場合にのみ機能します。

public class Bean {
  public void doStuff() {
    doTransactionStuff();
  }
  @Transactional
  public void doTransactionStuff() {

  }
}

これにより、トランザクションが開きます。

Bean bean = (Bean)appContext.getBean("bean");
bean.doTransactionStuff();

これはしません:

Bean bean = (Bean)appContext.getBean("bean");
bean.doStuff();

スプリングリファレンス:@Transactionalの使用

注:プロキシモード(デフォルト)では、プロキシを介して着信する「外部」メソッド呼び出しのみがインターセプトされます。つまり、呼び出されたメソッドに@Transactionalのマークが付いていても、「自己呼び出し」、つまりターゲットオブジェクト内のメソッドがターゲットオブジェクトの他のメソッドを呼び出すと、実行時に実際のトランザクションにつながりません。

自己呼び出しもトランザクションでラップされることが予想される場合は、AspectJモードの使用を検討してください(以下を参照)。この場合、そもそもプロキシはありません。代わりに、@Transactionalをあらゆる種類のメソッドの実行時の動作に変換するために、ターゲットクラスが「ウィーブ」されます(つまり、そのバイトコードが変更されます)。

28
Juha Syrjälä

はい、プライベートメソッドで@Transactionalを使用することは可能ですが、他の人が述べたように、これはそのままでは機能しません。 AspectJを使用する必要があります。動作させる方法を見つけるのに時間がかかりました。結果を共有します。

ロード時のウィービングの代わりにコンパイル時のウィービングを使用することを選択しました。これは全体的に優れたオプションだと思うからです。また、Java 8を使用しているため、いくつかのパラメーターを調整する必要があります。

まず、aspectjrtの依存関係を追加します。

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjrt</artifactId>
    <version>1.8.8</version>
</dependency>

次に、AspectJプラグインを追加して、Mavenで実際のバイトコードウィービングを行います(これは最小限の例ではない場合があります)。

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>aspectj-maven-plugin</artifactId>
    <version>1.8</version>
    <configuration>
        <complianceLevel>1.8</complianceLevel>
        <source>1.8</source>
        <target>1.8</target>
        <aspectLibraries>
            <aspectLibrary>
                <groupId>org.springframework</groupId>
                <artifactId>spring-aspects</artifactId>
            </aspectLibrary>
        </aspectLibraries>
    </configuration>
    <executions>
        <execution>
            <goals>
                <goal>compile</goal>
            </goals>
        </execution>
    </executions>
</plugin>

最後にこれを設定クラスに追加します

@EnableTransactionManagement(mode = AdviceMode.ASPECTJ)

これで、プライベートメソッドで@Transactionalを使用できるようになります。

このアプローチの1つの注意:IDEを設定して、AspectJを認識させる必要があります。そうしないと、たとえばEclipseを介してアプリを実行する場合、動作しない可能性があります。健全性チェックとして、直接Mavenビルドに対してテストしてください。

12
James Watkins

トランザクション内でプライベートメソッドをラップする必要があり、aspectjを使用したくない場合は、 TransactionTemplate を使用できます。

@Service
public class MyService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    private void process(){
        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus status) {
                processInTransaction();
            }
        });

    }

    private void processInTransaction(){
        //...
    }

}
4
loonis

答えはノーです。 Spring Reference:Using @Transactional を参照してください:

@Transactional注釈は、インターフェース定義、インターフェースのメソッド、クラス定義、またはクラスのpublicメソッドの前に配置できます。

3
ivy

Spring Docsはそれを説明しています

プロキシモード(デフォルト)では、プロキシを介して着信する外部メソッド呼び出しのみがインターセプトされます。つまり、ターゲットオブジェクトの別のメソッドを呼び出すターゲットオブジェクト内のメソッドは、実際には、呼び出されたメソッドが@Transactionalでマークされていても、実行時に実際のトランザクションにつながりません。

自己呼び出しもトランザクションでラップされることが予想される場合は、AspectJモードの使用を検討してください(下の表のmode属性を参照)。この場合、そもそもプロキシはありません。代わりに、@ Transactionalをあらゆる種類のメソッドの実行時動作に変換するために、ターゲットクラスが織り込まれます(つまり、そのバイトコードが変更されます)。

別の方法はユーザーですBeanSelfAware

3
user536161

@ loonisを推奨 と同じ方法で TransactionTemplate を使用して、このヘルパーコンポーネント(Kotlin)を使用できます。

@Component
class TransactionalUtils {
    /**
     * Execute any [block] of code (even private methods)
     * as if it was effectively [Transactional]
     */
    @Transactional
    fun <R> executeAsTransactional(block: () -> R): R {
        return block()
    }
}

使用法:

@Service
class SomeService(private val transactionalUtils: TransactionalUtils) {

    fun foo() {
        transactionalUtils.executeAsTransactional { transactionalFoo() }
    }

    private fun transactionalFoo() {
        println("This method is executed within transaction")
    }
}

TransactionTemplateは既存のトランザクションを再利用するかどうかわかりませんが、このコードは間違いなく利用します。

0
Lu55