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


196

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

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

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

  }
}

...

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

回答:


163

質問は非公開でも公開でもありません。問題は、どのように呼び出され、どのAOP実装を使用するかです。

SpringプロキシAOP(デフォルト)を使用する場合、Springが提供するすべてのAOP機能(など@Transational)は、呼び出しがプロキシを経由する場合にのみ考慮されます。-これは通常、アノテーション付きメソッドが別の Bean から呼び出された場合です。

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

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

@See Springリファレンス:第9.6章9.6プロキシメカニズム

私見では、問題を克服するSpringプロキシの代わりに、aspectJモードを使用する必要があります。また、AspectJトランザクションアスペクトは、プライベートメソッド(Spring 3.0でチェック済み)にも組み込まれています。


4
両方の点が必ずしも正しいわけではありません。1つ目は正しくありません。プライベートメソッドリフレクトで呼び出すことできますが、プロキシ検出ロジックはそうしないことを選択します。2番目の点は、インターフェースベースのJDKプロキシにのみ当てはまり、CGLIBサブクラスベースのプロキシには当てはまりません。
skaffman 2010

@skaffman:1-私は自分の文をより正確にします。2。しかし、デフォルトのプロキシはインターフェイスベースです-そうではありませんか?
ラルフ

2
ターゲットがインターフェースを使用するかどうかによって異なります。そうでない場合は、CGLIBが使用されます。
skaffman 2010

canuは、cglibができないのにaspectjができるのに、その理由や参考文献を教えてください。
フィル

1
回答ブロックのリンクからの参照。SpringProxies [デフォルト環境]を使用する場合は、doStuff()に注釈を付け、((Bean)AopContext.currentProxy())。doPrivateStuff();を使用してdoPrivateStuff()を呼び出します。伝播が再実行されると、1つの同じトランザクションで両方のメソッドが実行されます[デフォルト環境]。
Michael Ouyang

219

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

これはSpring Manualの10.5.6章に記載されています:

メソッドの可視性と @Transactional

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


あなたはこれについて確信を持っていますか?それが違いを生むとは思いません。
willcodejavaforfood

プロキシスタイルがCglibの場合はどうですか?
リリー

32

デフォルトでは、この@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、あらゆる種類のメソッドの実行時動作に変換するために、「織り込まれます」(つまり、そのバイトコードが変更されます)。


bean = new Bean();ですか?
willcodejavaforfood

いいえ。new Bean()でBeanを作成する場合、少なくともAspect-Jを使用しないと注釈は機能しません。
JuhaSyrjälä10年

2
ありがとう!これは私が観察していた奇妙な行動を説明しています。この内部メソッドの呼び出し制限はかなり直観的ではありません...
マヌエルアルダナ

私は、ハードな方法を「プロキシ経由で来て唯一の外部メソッドの呼び出しをインターセプトされ、」学んだ
asgs

13

はい、プライベートメソッドで@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つの注意点:AspectJを認識するようにIDEを構成する必要があります。そうでない場合、たとえばEclipse経由でアプリを実行する場合、機能しない可能性があります。健全性チェックとして、Mavenの直接ビルドに対してテストしてください。


プロキシメソッドがcglibの場合、メソッドをパブリックにする必要があるインターフェイスを実装する必要はありません。プライベートメソッドで@Transactionalを使用できますか?
リリー

はい、それはプライベートメソッドで動作し、インターフェイスはありません!AspectJが適切に設定されている限り、基本的に動作するメソッドデコレーターが保証されます。そして、user536161は彼の回答で、それが自己呼び出しでも機能することを指摘しました。それは本当にクールで、ほんの少し怖いです。
ジェームズワトキンス

12

トランザクション内でプライベートメソッドをラップする必要があり、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(){
        //...
    }

}

TransactionTemplate使用方法を示すのに適していますが、..RequiresTransactionではなく、2番目のメソッドを呼び出してください..InTransaction。1年後はいつでも読みたいものに名前を付けてください。また、2番目のプライベートメソッドが本当に必要かどうかを検討します。コンテンツを直接匿名execute実装に配置するか、乱雑になると、実装を別のサービスに分割して注釈を付けることができる可能性があります@Transactional
スタック

@ Stuck、2番目の方法は実際には必要ありませんが、プライベートメソッドに春のトランザクションを適用する方法である元の質問に答えます
loonis

はい、私はすでに答えを賛成しましたが、アーキテクチャの観点からこの状況は設計上の欠陥の潜在的な兆候だと思うので、それを適用する方法についていくつかのコンテキストと考えを共有したいと思いました。
スタック

5

春のドキュメントはそれを説明します

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

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

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


への参照を追加できますBeanSelfAwareか?それはSpringのクラスのようには見えません
昇順

@asgs仮に、それは自己注入についてです(それ自体のインスタンスがプロキシーにラップされたBeanを提供します)。stackoverflow.com/q/3423972/355438で例を確認できます。
Lu55、19年


1

@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既存のトランザクションを再利用するかどうかはわかりませんが、このコードは確実に再利用します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.