skarltjr / Memory_Write_Record

나의 모든 학습 기록
0 stars 0 forks source link

트랜잭션 롤백이 처리될까? with try/catch #139

Open skarltjr opened 1 year ago

skarltjr commented 1 year ago
innerService에서 발생한 예외를 
outerService에서 try / catch로 잡았을 때
트랜잭션은 롤백이 될까?

들어가기 앞서

  @Transactional
  public void someThing(int number) {
      try {
          for (int i = 1; i <= 10; i++) {
              if (i == 9) {
                  throw new RuntimeException("qwe");
              }
              myRepo.save(new MyEntity(null, i));
          }
      } catch (RuntimeException exception) {
          System.out.println("hqwewqewq");
      }
  }
innerService에서 예외를 잡는 경우 롤백없이 commit된다.
그 이유를 먼저 알아보면

TransactionAspectSupport.invokeWithinTransaction()

만약 UncheckedException과 Error가 발생하면 Throwable에 걸려 지금 문제가 있음을 파악하고
롤백을 처리한다.
그런데 이 경우는 먼저 try/catch로 RuntimeException을 잡았기에 Throwable에 걸리지 않는다.
아래처럼 브레이크 포인트 걸고 디버깅을 해보면 알 수 있다.

그럼 아래 경우도 위와 동일한지 확인해보자

OuterService

  @Transactional
  public void outer(int number) {
      try {
          myService2.someThing(number);
      } catch (RuntimeException exception) {
          log.warn(" catch exception ");
      }
  }

InnerService

  @Transactional
  public void someThing(int number) {
      for (int i = 1; i <= 10; i++) {
          if (i == 9) {
              throw new RuntimeException("qwe");
          }
          myRepo.save(new MyEntity(null, i));
      }
  }
예상은 트랜잭션이 시작된 매서드 내부에서 예외를 잡았으니 롤백없이 commit될거라 생각했지만
Throwable에 먼저 걸리고 롤백처리된다.

왜?

우선 트랜잭션 단위로 생각해보자

outerService -> innerService -> jpaRepository(crudRepository).save()
모두 기본 전파레벨인 required를 사용한다.

그리고
전파속성(*propagation*) 때문에 실제 트랜잭션이 재사용되더라도 트랜잭션 메서드의 반환시점마다 트랜잭션의 완료처리(completion)를 한다. 즉 save마다 완료처리가 수행된다.
물론 커밋이나 롤백같은 최종완료처리는 최초 트랜잭션이 반환될 때 일어난다.

그런데
innerService 또한 마찬가지다.
RuntimeException이 던져지면서 완료처리가된다. 
하지만!!!! 
innerService에서 시작된 곳에서 try/catch가 없다 지금
그 상위에 있다.
즉 여기서 예외를 잡을 수 없고 그렇기에 rollback marking을 진행한다.

그리고 상위 outerService가 트랜잭션 종료 전 완료처리를 진행할때
rollback-only라는 마킹이 되어있기 때문에 
아! 롤백해야하는구나라고 판단하여 롤백을 진행한다.
outerService 시작
       - innerService 시작
            - save() 시작
            - save() 완료
       - innerService 완료
outerService 완료

결국 required 전파레벨로 각 트랜잭션이 앞선 트랜잭션에 참여하더라도
매서드 반환 시점마다 트랜잭션 완료처리가 진행되는데

innerService 또한 예외가 발생하고 반환 시점에 완료처리 중 예외가 발생했지만
현재 시점에서 exception catch는 걸리지 않기에 rollback-only 마킹을 진행

이후 최상위 outerService가 매서드 반환 시점에 완료 처리를 진행하다
rollback-only 마킹되어있는걸 보고 아! 롤백 처리해야하네. 진행
그럼 조금 다른 예시로 다시 생각해보자
inner가 propagation required_new로 새 트랜잭션을 시작한 상황이라면?

outer

이 경우는 outer와 inner는 서로다른 트랜잭션 inner에서 발생한 예외를 outer에서 잡았고 서로 다른 트랜잭션이기때문에 inner는 롤백, outer는 이에 영향을 받지 않는다


https://techblog.woowahan.com/2606/