403

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

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

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

注:このメカニズムはプロキシに基づいているため、プロキシを介して着信する「外部」メソッド呼び出しのみがインターセプトされます。つまり、「自己呼び出し」、つまりターゲットオブジェクト内のメソッドがターゲットオブジェクトの他のメソッドを呼び出すと、呼び出されたメソッドに@Transactional!のマークが付いていても、実行時に実際のトランザクションが発生することはありません。

出典:http ://static.springsource.org/spring/docs/2.0.x/reference/transaction.html

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

4

6 に答える 6

294

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

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

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

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

BeanFactoryPostProcessorSpring 1.xのタイムフレームで書き戻されたため、コードが少し異なることに注意してください。しかし、うまくいけば、それはあなたにアイデアを与えるでしょう。私はおそらく利用可能にすることができる更新されたバージョンを持っています。

于 2009-07-08T16:50:34.223 に答える
221

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

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

「外部メソッド」については、Beanが独自のメソッドの1つを呼び出す場合、プロキシを介して呼び出すことはありません。SpringはBeanをプロキシでラップしていることを忘れないでください、あなたのBeanはそれを認識していません。Beanの「外部」からの呼び出しのみがプロキシを通過します。

それは役に立ちますか?

于 2009-07-08T16:59:09.683 に答える
58

視覚的な人間として、私はプロキシパターンのシーケンス図を検討するのが好きです。矢印の読み方がわからない場合は、最初の矢印を次のように読みClientますProxy.method()

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

プロキシパターンのシーケンス図 (私はその起源を述べたことを条件に写真を投稿することを許可されました。著者:Noel Vaes、ウェブサイト:https ://www.noelvaes.eu )

于 2016-10-21T09:40:48.420 に答える
48

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

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

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

entity1、entity2、entity3を保存しているとしましょう。ここで、entity3を保存しているときに例外が発生し、entity1とentity2が同じトランザクションで発生するため、entity1とentity2はentity3でロールバックされます。

取引 :

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

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

于 2018-01-30T09:14:44.073 に答える
7

既存の答えはすべて正しいですが、私はこの複雑なトピックだけを与えることはできないと感じています。

包括的で実用的な説明については、このSpring @Transactional In-Depthガイドを参照してください。このガイドでは、多くのコード例を使用して、トランザクション管理を約4000語でカバーするように最善を尽くしています。

于 2020-03-03T15:09:22.280 に答える
5

遅いかもしれませんが、プロキシに関連する懸念をうまく説明しているものに出くわしました(プロキシを介して着信する「外部」メソッド呼び出しのみが傍受されます)。

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

@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/から取得したコードスニペット

于 2019-12-18T14:42:13.583 に答える