web-dev-qa-db-ja.com

Springネストされたトランザクション

私のSpring Bootプロジェクトでは、次のサービスメソッドを実装しました:

_@Transactional
public boolean validateBoard(Board board) {
    boolean result = false;
    if (inProgress(board)) {
        if (!canPlayWithCurrentBoard(board)) {
            update(board, new Date(), Board.AFK);
            throw new InvalidStateException(ErrorMessage.BOARD_TIMEOUT_REACHED);
        }
        if (!canSelectCards(board)) {
            update(board, new Date(), Board.COMPLETED);
            throw new InvalidStateException(ErrorMessage.ALL_BOARD_CARDS_ALREADY_SELECTED);
        }
        result = true;
    }
    return result;
}
_

このメソッドの内部では、updateと呼ばれる別のサービスメソッドを使用します。

_@Transactional(propagation = Propagation.REQUIRES_NEW)
public Board update(Board board, Date finishedDate, Integer status) {
    board.setStatus(status);
    board.setFinishedDate(finishedDate);

    return boardRepository.save(board);
}
_

updateメソッドで開始された所有者トランザクションとは別に、validateBoardメソッドでデータベースへの変更をコミットする必要があります。現在、例外が発生した場合、変更はロールバックされています。

@Transactional(propagation = Propagation.REQUIRES_NEW)を使用しても動作しません。

Springでこれを正しく行い、ネストされたトランザクションを許可するにはどうすればよいですか?

23
alexanoid

このドキュメントはあなたの問題を扱っています- https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/data-access.html#transaction-declarative-annotations

プロキシモード(デフォルト)では、プロキシを介して着信する外部メソッド呼び出しのみがインターセプトされます。つまり、実際には、ターゲットオブジェクトの別のメソッドを呼び出すターゲットオブジェクト内のメソッドは、呼び出されたメソッドが@Transactionalでマークされていても、実行時に実際のトランザクションにつながりません。また、期待される動作を提供するためにプロキシを完全に初期化する必要があるため、初期化コードでこの機能に依存しないようにしてください(@PostConstruct)。

ただし、AspectJモードに切り替えるオプションがあります

24
Jakub Bibro

「self」jnjectパートナを使用すると、この問題を解決できます。

以下のようなサンプルコード:

@Service @Transactional
public class YourService {
   ... your member

   @Autowired
   private YourService self;   //inject proxy as instance member variable ;

   @Transactional(propagation= Propagation.REQUIRES_NEW)
   public void methodFoo() {
      //...
   }

   public void methodBar() {
      //call self.methodFoo() rather than this.methodFoo()
      self.methodFoo();
   }
}

ポイントは、「これ」ではなく「自己」を使用することです。

5
shenyu1997

ネストされたトランザクションに関する基本的な経験則は、それらが基礎となるデータベースに完全に依存していることです。つまり、ネストされたトランザクションのサポートとその処理はデータベースに依存し、データベースによって異なります。一部のデータベースでは、ネストされたトランザクションによって行われた変更は、ネストされたトランザクションがコミットされるまで「ホスト」トランザクションから認識されません。これは、@ Transactionalのトランザクション分離(isolation = "")を使用して実現できます。

例外がスローされる場所、つまり親メソッド「validateBoard」または子メソッド「update」から、コード内の場所を識別する必要があります。

コードスニペットは、例外を明示的にスローしていることを示しています。

知っておく必要があります::

デフォルトの設定では、Spring Frameworkのトランザクションインフラストラクチャコードは、実行時の未チェックの例外の場合にのみトランザクションをロールバックにマークします。つまり、スローされた例外がRuntimeExceptionのインスタンスまたはサブクラスである場合です。

ただし、@ Transactionalは、チェック済み例外に対してトランザクションをロールバックすることはありません。

したがって、Springでは次を定義できます。

  • トランザクションをロールバックする必要がある例外
  • トランザクションをロールバックしてはならない例外

子メソッドに注釈を付けてみてください:@Transactional(no-rollback-for = "ExceptionName")または親メソッドで更新します。

2
Philip Dilip

問題は、同じプロキシ内の別のメソッドからのメソッドの呼び出しです。それは自己呼び出しです。あなたの場合、別のサービス内でメソッドを移動せずに簡単に修正できます(自己呼び出しを避けるために、あるサービスから別のサービスにメソッドを移動するためだけに別のサービスを作成する必要があるのはなぜですか?)、2番目のメソッドを呼び出すだけです現在のクラスから直接ではなく、スプリングコンテナから。この場合、self-invocatioではなくトランザクションでプロキシの2番目のメソッドを呼び出します。

この原則は、トランザクションプロキシだけでなく、自己呼び出しが必要なプロキシオブジェクトに役立ちます。

_@Service
class SomeService ..... {
    -->> @Autorired
    -->> private ApplicationContext context;
    -->> //or with implementing ApplicationContextAware

    @Transactional(any propagation , it's not important in this case)
    public boolean methodOne(SomeObject object) {
      .......
       -->> here you get a proxy from context and call a method from this proxy
       -->>context.getBean(SomeService.class).
            methodTwo(object);
      ......
   }

    @Transactional(any propagation , it's not important in this case)public boolean 
    methodTwo(SomeObject object) {
    .......
   }
}
_

context.getBean(SomeService.class).methodTwo(object);コンテナを呼び出すと、プロキシオブジェクトが返され、このプロキシでは、トランザクションでmethodTwo(...)を呼び出すことができます。

1
xyz

updateメソッドのトランザクションアノテーションは、同じクラスのメソッドから呼び出された場合、Springトランザクションインフラストラクチャによって考慮されません。 Springトランザクションインフラストラクチャの仕組みの詳細については、 this を参照してください。

1
Shailendra