1 of 30

@Transactional 은 어떻게 동작할까?

강대명(charsyam@naver.com)

2 of 30

강대명 소개

  • 현) 레몬트리 CTO

  • 구) 위버스 데이터 엔지니어
  • 구) 유데미 데이터 엔지니어
  • 구) 카카오 백엔드 엔지니어
  • 구) 네이버 백엔드 엔지니어
  • 오픈소스 컨트리뷰터

3 of 30

@Transactional 은 어떻게 동작할까?

4 of 30

4가지 없는 샘플

1

2

3

4

@Transactional

Annotation

False

False

True

True

Exception

False

True

False

True

비고

그대로 저장

그대로 저장

그대로 저장

Rollback

5 of 30

4가지 없는 샘플…(엉?)

6 of 30

@Transactional Annotation이 있으면

Exception 이 발생하면 자동으로 rollback 이 된다!!!

7 of 30

AOP의 마법…

8 of 30

@Transactional 이 없는 경우

9 of 30

@Transactional 이 없을때 생기는 Call Stack

  • method4 부터 save 으로 실제로 넘어오는 callstack
  • CGLIB 으로 인해 생기는 Proxy 코드들이 많이 존재한다.

10 of 30

CglibAopProxy 가 호출된다.

11 of 30

@Transactional 이 있는 경우

12 of 30

@Transactional Annotation 을 이용했을 때 생기는 Call Stack

  • method4 부터 saveWithTransactionException 으로 실제로 넘어오는 callstack
  • CGLIB 으로 인해 생기는 Proxy 코드들이 많이 존재한다.

13 of 30

똑같이 CglibAopProxy 가 호출된다.

14 of 30

추가로 TransactionInterceptor 가 호출된다.

15 of 30

TransactionInterceptor #1

  • invokeWithinTransaction 이 호출된다.

16 of 30

TransactionInterceptor #2 - ReactiveTransactionManager

  • reactiveAdapterRegistry가 있고 ™ 이 ReactiveTransactionManager 일때…(무슨소리인가?)

17 of 30

TransactionInterceptor #4

  • CallbackPreferringPlatformTransactionManager 일때

18 of 30

실제 주요 Proxy 코드

  • invocation.proceedWithInvocation() 실행 - 실제 코드가 동작
  • 오류가 발생하면 completeTransactionAfterThrowing 안에서 rollback 이 처리가 된다.
    • 오류에 따라서 Rollback을 안할 수도 있다.
    • 이 때 상태가 저장된다. ThreadLocalStorage 사용
  • 성공적이면 commitTransactionAfterReturing 이 실행되어서 commit 이 발생한다.

19 of 30

여기서 주의!!!

20 of 30

Proxy 코드는 외부에서 해당 Annotation 이 있는 메서드를 호출할 때 생성된다.

21 of 30

내부에서 호출 하는 경우 #1

  • saveWithTransactionExceptionIndirect() 에서 saveWithTransactionException() 을 호출 할 경우

22 of 30

내부에서 호출 하는 경우 #2

  • 호출시의 Call Stack을 보면 최초로 saveWithTrnasactionExceptionIndirect()는 Proxy코드가 생성되지만
  • saveWithTrnasactionExceptionIndirect() -> saveWithTransactionException() 바로 호출된다.

23 of 30

Proxy 가 어떻게 호출하는지 모르면…

Transaction이 Rollback 안되는 경우가 발생할 수

있다. 딴 것들도 마찬가지!!!

24 of 30

@Transactional 쓸 때 주의할 것

One More Thing

25 of 30

try-catch 로 exception을 잡아도

rollback 될 수 있다.

26 of 30

새로운 케이스

  • @transactional 이 또 다른 @transactional을 호출했는데, 이미 rollback 이 된 경우.
    • 이건 내부적으로 무조건 rollback이 먼저 설정됨.

27 of 30

새로운 @transactional 의 호출

  • try-catch 로 secondService.throwException()의 오류를 잡는다.
  • 그러나 이미 내부에서 @Transactional 의 값은 rollback으로 설정이 되어버린다.

28 of 30

새로운 @transactional 의 호출

  • try-catch 로 secondService.throwException()의 오류를 잡는다.
  • 그러나 이미 내부에서 이전 exception이 안잡혔기 때문에 @Transactional 의 값은 rollback으로 설정이 되어버린다.

  • 그래서 다음 호출한 saveWithTransactionSubException() 이 commit 을 하려고 할 때 이미 내부 객체가 rollback 설정이 되어서 UnexpectedRollbackException 이 터지면서 Transaction silently rolled back because it has been marked as rollback-only 메시지를 보게 된다.

29 of 30

Reference

  • 응? 이게 왜 롤백되는거지? - 우아한 기술블로그(https://techblog.woowahan.com/2606/)
  • 테드의 기술블로그 - https://hwannny.tistory.com/98
  • [Spring] AOP와 @Transactional 의 동작 원리 - https://velog.io/@ann0905/AOP%EC%99%80-Transactional%EC%9D%98-%EB%8F%99%EC%9E%91-%EC%9B%90%EB%A6%AC

30 of 30

감사합니다.