春-@Transactional-バックグラウンドで何が起こりますか?


334

メソッドに注釈を付けたときに実際に何が起こるか知りたいの@Transactionalですが?もちろん、Springはそのメソッドをトランザクションにラップすることを知っています。

しかし、私には以下の疑問があります。

  1. Springがプロキシクラスを作成すると聞きましたが?誰かがより多くでこれを説明でき深さそのプロキシクラスに実際に存在するのは何ですか?実際のクラスはどうなりますか?そして、どうすればSpringの作成されたプロキシクラスを見ることができますか
  2. また、Springのドキュメントで次のことを読みました。

注:このメカニズムはプロキシに基づいているため、プロキシ経由で着信する「外部」メソッド呼び出しのみがインターセプトされます。これは、「自己呼び出し」、つまりターゲットオブジェクト内の他のメソッドを呼び出すターゲットオブジェクト内のメソッドは、呼び出されたメソッドが@Transactional

ソース:http : //static.springsource.org/spring/docs/2.0.x/reference/transaction.html

自己呼び出しメソッドではなく、外部メソッド呼び出しのみがトランザクションの下にあるのはなぜですか?


2
関連の議論はここにある:stackoverflow.com/questions/3120143/...
dma_k

回答:


255

これは大きな話題です。Springのリファレンスドキュメントには、複数の章が含まれています。Springの宣言型トランザクションサポートは、その基礎でAOPを使用するため、アスペクト指向プログラミングトランザクションに関するものを読むことをお勧めします。

ただし、非常に高いレベルでは、Springはクラス自体またはメンバーに対して@Transactionalを宣言するクラスのプロキシを作成します。プロキシは実行時にほとんど見えません。これは、Springがプロキシされるオブジェクトへのメソッド呼び出しの前、後、または周囲に動作を注入する方法を提供します。トランザクション管理は、フックできる動作のほんの一例です。セキュリティチェックは別の動作です。また、ロギングなどのために、独自のものを提供することもできます。したがって、メソッドに@Transactionalで注釈を付けると、Springは、注釈を付けるクラスと同じインターフェイスを実装するプロキシを動的に作成します。また、クライアントがオブジェクトを呼び出すと、呼び出しが傍受され、プロキシメカニズムを介して動作が挿入されます。

ちなみに、EJBのトランザクションも同様に機能します。

ご覧のとおり、プロキシメカニズムは、外部オブジェクトからの呼び出しがあった場合にのみ機能します。オブジェクト内で内部呼び出しを行う場合、プロキシをバイパスする「this」参照を通じて実際に呼び出しを行っています。ただし、その問題を回避する方法はいくつかあります。このフォーラムの投稿では、BeanFactoryPostProcessorを使用して、プロキシのインスタンスを実行時に「自己参照」クラスに挿入する1つの方法について説明します。この参照を「me」というメンバー変数に保存します。次に、スレッドのトランザクションステータスの変更を必要とする内部呼び出しを行う必要がある場合、プロキシを介して呼び出しを指示します(例: " me.someMethod()"。)フォーラムの投稿でさらに詳しく説明します。BeanFactoryPostProcessorコードは、Spring 1.xのタイムフレームで書き戻されたため、少し異なることに注意してください。私はおそらく利用可能にすることができます。


4
>>プロキシは実行時にほとんど見えませんOh !! 私はそれらを見て興味があります:)残り..あなたの答えは非常に包括的でした。これは、あなたが私を助けているのは2回目です。すべての助けをありがとう。
ピーク時の2009

17
問題ない。デバッガーを使用すると、プロキシコードを確認できます。それがおそらく最も簡単な方法です。魔法はありません。これらは、Springパッケージ内の単なるクラスです。
Rob H

また、@ Transactionアノテーションを持つメソッドがインターフェースを実装している場合、Springは動的プロキシAPIを使用してトランザクション化を注入し、プロキシ使用しません。トランザクション化されたクラスには、どのような場合でもインターフェイスを実装することを好みます。
マイケルウィルズ

1
私も(私が思うように明示的なワイヤリングを使用してそれを行うために)スキームを見つけましたが、そうする場合は、おそらくリファクタリングする方が良いので、する必要がある。しかし、はい、それは時々非常に扱いにくいかもしれません!
ドナルフェロー

2
2019: この回答は古くなっているため、を使用してプロキシバイパスせずにオブジェクト内で内部呼び出しを行う必要がBeanFactoryPostProcessorある場合を説明する、参照されたフォーラム投稿はもう利用できません 。ただし、(私の意見では)この回答で説明されている非常に類似した方法があります: stackoverflow.com/a/11277899/3667003 ...そしてスレッド全体でのさらなる解決策も。
Z3d4s

195

SpringがBean定義をロードし、@Transactional注釈を探すように構成されている場合、Springは実際のBeanの周囲にこれらのプロキシオブジェクトを作成します。これらのプロキシオブジェクトは、実行時に自動生成されるクラスのインスタンスです。メソッドが呼び出されたときのこれらのプロキシオブジェクトのデフォルトの動作は、「ターゲット」Bean(つまり、Bean)で同じメソッドを呼び出すだけです。

ただし、プロキシにはインターセプターを指定することもでき、存在する場合、これらのインターセプターは、ターゲットBeanのメソッドを呼び出す前にプロキシーによって呼び出されます。でアノテーションが付けられたターゲットBeanの場合@Transactional、Springはを作成TransactionInterceptorし、生成されたプロキシオブジェクトに渡します。そのため、クライアントコードからメソッドを呼び出すと、プロキシオブジェクトのメソッドが呼び出され、最初にTransactionInterceptor(トランザクションを開始する)が呼び出され、次にターゲットBeanのメソッドが呼び出されます。呼び出しが完了するとTransactionInterceptor、トランザクションがコミットまたはロールバックされます。これはクライアントコードに対して透過的です。

「外部メソッド」については、Beanが独自のメソッドの1つを呼び出す場合、プロキシ経由では行われません。SpringはBeanをプロキシーでラップします。Beanはそれを認識していません。Beanの「外部」からの呼び出しのみがプロキシを通過します。

それは役に立ちますか?


36
>覚えておいてください、SpringはあなたのBeanをプロキシでラップします、あなたのBeanはそれを知りません。すばらしい答えです。助けてくれてありがとう。
ピーク時の2009

プロキシとインターセプターの素晴らしい説明。Springがプロキシオブジェクトを実装してターゲットBeanへの呼び出しをインターセプトすることを理解しました。ありがとうございました!
dharag 2013

私は、この絵は私に多くのことができますあなたは春のドキュメントのこの絵を記述しようとしていると思うと見て:docs.spring.io/spring/docs/4.2.x/spring-framework-reference/...
WesternGun

44

視覚的な人間として、私はプロキシパターンのシーケンス図を検討します。:あなたが矢を読み取る方法がわからない場合、私はこのような最初の読みClientが実行しますProxy.method()

  1. クライアントは自分の観点からターゲット上のメソッドを呼び出し、プロキシによって黙って傍受されます
  2. beforeアスペクトが定義されている場合、プロキシはそれを実行します
  3. 次に、実際のメソッド(ターゲット)が実行されます
  4. アフターリターンとアフタースローは、メソッドが戻った後、またはメソッドが例外をスローした場合に実行されるオプションの側面です。
  5. その後、プロキシは後のアスペクトを実行します(定義されている場合)
  6. 最後に、プロキシは呼び出し元のクライアントに戻ります

プロキシパターンシーケンス図 (写真の出所を明記した条件で写真を投稿することを許可されました。著者:Noel Vaes、ウェブサイト:www.noelvaes.eu)


27

最も簡単な答えは次のとおりです。

どちらのメソッドでも@Transactional、トランザクションの境界を宣言し、メソッドが完了すると境界が終了します。

JPA呼び出しを使用している場合、すべてのコミットはこのトランザクション境界内にあります。

entity1、entity2、およびentity3を保存するとします。ここで、entity3の保存中に例外が発生します。enitiy1とentity2が同じトランザクションに入ると、entity1とentity2はentity3でロールバックされます。

取引:

  1. entity1.save
  2. entity2.save
  3. entity3.save

例外が発生すると、DBを使用するすべてのJPAトランザクションがロールバックされます。内部では、SpringによってJPAトランザクションが使用されます。


2
「A̶n̶y̶例外により、DBとのすべてのJPAトランザクションがロールバックされます。」 :RuntimeExceptionのみがロールバックになります。例外を確認しましたが、ロールバックされません。
アルジュン

2

遅いかもしれませんが、プロキシに関する懸念を説明する何かに遭遇しました(プロキシ経由で着信する「外部」メソッド呼び出しのみが傍受されます)。

たとえば、次のようなクラスがあるとします

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
    }

    public void doSomethingSmall(int x){
        System.out.println("I also do something small but with an int");    
  }
}

そしてあなたはこのように見えるアスペクトを持っています:

@Component
@Aspect
public class CrossCuttingConcern {

    @Before("execution(* com.intertech.CoreBusinessSubordinate.*(..))")
    public void doCrossCutStuff(){
        System.out.println("Doing the cross cutting concern now");
    }
}

次のように実行すると:

 @Service
public class CoreBusinessKickOff {

    @Autowired
    CoreBusinessSubordinate subordinate;

    // getter/setters

    public void kickOff() {
       System.out.println("I do something big");
       subordinate.doSomethingBig();
       subordinate.doSomethingSmall(4);
   }

}

上記のコードを指定して、上記のkickOffを呼び出した結果。

I do something big
Doing the cross cutting concern now
I did something small
Doing the cross cutting concern now
I also do something small but with an int

しかし、コードを次のように変更すると

@Component("mySubordinate")
public class CoreBusinessSubordinate {

    public void doSomethingBig() {
        System.out.println("I did something small");
        doSomethingSmall(4);
    }

    public void doSomethingSmall(int x){
       System.out.println("I also do something small but with an int");    
   }
}


public void kickOff() {
  System.out.println("I do something big");
   subordinate.doSomethingBig();
   //subordinate.doSomethingSmall(4);
}

メソッドが内部的に別のメソッドを呼び出すので、インターセプトされず、出力は次のようになります。

I do something big
Doing the cross cutting concern now
I did something small
I also do something small but with an int

あなたはそれを行うことでこれを回避することができます

public void doSomethingBig() {
    System.out.println("I did something small");
    //doSomethingSmall(4);
    ((CoreBusinessSubordinate) AopContext.currentProxy()).doSomethingSmall(4);
}

抜粋:https : //www.intertech.com/Blog/secrets-of-the-spring-aop-proxy/


0

既存の答えはすべて正解ですが、この複雑なトピックだけを与えることはできません。

包括的で実用的な説明については、このSpring @Transactional In-Depthガイドをご覧になることをお勧めします。このガイドでは、トランザクション管理を最大4000の簡単な言葉でカバーし、多くのコード例を紹介しています。

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