zomggang
@zomggang (ZOM GGANG)

Are you sure you want to delete the question?

Leaving a resolved question undeleted may help others!

二重@Transactionalの場合UnexpectedRollbackExceptionエラーが出る。どのようにキャッチできるか

Discussion

Closed

解決したいこと/解決したけど、方法が合っているか自信がない

外部・内部に@Transactionalを付けたら、外部で
UnexpectedRollbackExceptionが発生する

class TestLogic{
@Transactional
  public void testMethod(TestModel item) {
    testRepository.save(item);
    throw new NullPointerException("error check nullPointerExcpetion");
  }
}

@Transactional
public ResModel testMain {
 try {
      testLogic.testMethod(item);
    } catch (Exception e) {
      log.warn("登録に失敗しました。" + itemCode, e);
      return resModel.danger("登録に失敗しました");// resModelは画面部に伝うもの
    }

}

発生している問題・エラー

外部Exceptionへ
UnexpectedRollbackException
org.springframework.transaction.UnexpectedRollbackException: JTA transaction unexpectedly rolled back (maybe due to a timeout); nested exception is javax.transaction.RollbackException: Transaction set to rollback only
    at org.springframework.transaction.jta.JtaTransactionManager.doCommit(JtaTransactionManager.java:1038)


そのせいでweb上にも
org.springframework.web.client.HttpServerErrorException$InternalServerError: 500 : [{"success":false,"warning":false,"error":"500","error_description":"Internal Server Error"}]
    at org.springframework.web.client.HttpServerErrorException.create(HttpServerErrorException.java:100)
    at org.springframework.web.client.DefaultResponseErrorHandler.handleError(DefaultResponseErrorHandler.java:188)

自分で試したこと

ここに問題・エラーに対して試したことを記載してください。

@Transactional
public ResModel testMain {
呼び出し先にtransaciontalアノテーションを削除したら解決
public ResModel testMain {


参照) https://minokuba.hatenablog.com/entry/20110503/1304428731
https://cobbybb.tistory.com/17
https://terasolunaorg.github.io/guideline/5.0.2.RELEASE/ja/ImplementationAtEachLayer/DomainLayer.html

image.png

  • propagation:トランザクションの処理中、他のトランザクションを呼び出す時に何をするか決めるoption
    • REQUIRED (Default):既に進行中のトランザクションがあれば、該当トランザクションの属性を従がる。または新しいトランザクションを生成する 디폴트값이 새로 트랜잭션을 여는 것이 아니기 때문에, 외부 클래스에서 @Transactional이 선언되어있다고 한들 같은 트랜잭션을 이용
  1. Controller로부터 트랜잭션 관리대상의 Service의 메소드를 호출한다. 이 지점에서 개시된 트랜잭션이 존재하지 않기 때문에, TransactionInterceptor에 의해 새로운 트랜잭션이 개시된다
  2. TrasactionInterceptor는 트랜잭션개시 후에, 트랜잭션 관리대상의 메소드를 호출한다
  3. Service로부터 트랜잭션 관리 대상의 SharedService의 메소를 호출한다. 이 지점에서 개시된 트랜잭션이 존재하기 때문에 TransactionInterceptor는 새로운 트랜잭션을 열지 않고 , 개시된 트랜잭션에 참가한다.
  4. TranscationInterceptor는 개시된 트랜잭션에 참가한 후에 트랜잭션관리 대상의 메소드를 부른다.
  5. TransactionInterceptor는 처리 결과에 따라 커밋 혹은 롤백을 시행하고 트랜잭션을 종료한다.

Note
org.springframework.transaction.UnexpectedRollbackExceptionが発生する理由
トランザクションの伝播方法を「REQUIRED」にした場合、物理的なトランザクションは一つだが、Spring Frameworkでは内部的なトランザクション制御境界が設けられている。 上記例だと、SharedServiceが呼び出された際に実行されるTransactionInterceptorが、内部的なトランザクション制御を行っている。 そのため、SharedServiceでロールバック対象の例外が発生した場合、TransactionInterceptorによって、 トランザクションはロールバック状態(rollback-only)に設定され、トランザクションをコミットすることはできなくなる。 この状態でトランザクションのコミットを行おうとすると、Spring Frameworkは、UnexpectedRollbackExceptionを発生させ、トランザクション制御に矛盾が発生している事を通知してくれる。 UnexpectedRollbackExceptionが発生した場合、rollbackForおよびnoRollbackForの定義に、矛盾がないか、確認すること。

Note
org.springframework.transaction.UnexpectedRollbackException가 일어난 이유
트랜잭션의 propagation [REQUIRED]인 경우 물리적인 트랜젹션은 하나이지만, Spring Framework에는 내부적 트랜잭션제어경계가 설계되어있다. 상기의 예라면 SharedService가 호출된 때 실행된 TransactionInterceptor가 내부적인 트랜잭션 처리를 실행하기 때문에 SharedService에 롤백대상에 예외가 발생한 경우 TransactionInterceptor에 의해 트랜잭션을 롤백한상태(rollback-only)를 설정, 트랜잭션을 커밋하는 것이 불가능하게 된다. 이 상황에서는 트랜잭션의 커밋을 일으키기 위해서는 Spring Framework는 UnexpectedRollbackException를 발생시켜, 트랜잭션 제어의 모순이 발생한 것을 통지한다. UnexpectedRollbackException가 발생한 경우, rollbackFor 및 noRollbackFor의 정의에 모순이 있는가 없는가를 확인한다.

이미 롤백하기로 결정했는데 뒤에 다시 commit하려고 하다보니 에러가 발생한듯하다.

解決方法

  1. propagationの設定を変更
  2. 二重@Transactionalは付けない
0

Your answer might help someone💌