Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

@Transactional 동작 복기 #141

Open
skarltjr opened this issue Jan 19, 2023 · 5 comments
Open

@Transactional 동작 복기 #141

skarltjr opened this issue Jan 19, 2023 · 5 comments
Labels

Comments

@skarltjr
Copy link
Owner

skarltjr commented Jan 19, 2023

상황을 정리하여 잘못알거나 제대로 확인해야할부분들을 다시 정리해보자
내가 잘못 생각한 부분은 전파라는 개념이다.

트랜잭션 전파란
즉, 이미 시작된 트랜잭션이 존재하는 상태에서
!!! 새로운 !!! 트랜잭션이 생성될 때!!! 
그 때
이전 트랜잭션에 참여할것인가,
독자적인 새로운 트랜잭션을 생성할까... 를 고민하는것
  • 주석 2023-01-20 100331
그러니까 여기서는 outer에서 트랜잭션이 시작.
inner의 트랜잭션은 동작자체를 안한다.


그럼 inner에서는 트랜잭션 자체가 시작되지 않는 상황이니
새로운 트랜잭션을 만들고 앞선 트랜잭션에 참여할것인지 말것인지 등을 결정하지 않는다.
그냥 이미 outer에서 시작된 트랜잭션이 있고 이를 활용한다.
실제로 디버깅해보면 아래와 같다
@skarltjr
Copy link
Owner Author

동일빈 호출문제

  • 주석 2023-01-19 102236
  • 주석 2023-01-19 102429
상황 : 
1. controller -> outer() -> inner()순으로 호출
2. inner()에서 runtimeException 발생
3. 부분롤백 발생

원인 :
1. 스프링 aop의 프록시는 기본적으로 스프링 빈을 대상으로 서로 다른 빈에서(외부,ex.controller -> service)호출하는
경우에 동작한다.
2. 이 상황은 외부에서 호출되는 outer()에는 @Transactional 어노테이션이 없고 트랜잭션이 시작되지 않는다.
3. 하지만 crudRepository의 구현체인 simpleJpaRepository.save()는 기본적으로 @Transactional 어노테이션이 부착되어있고
4. 즉 각각의 save는 자신만의 트랜잭션으로 동작

결과 :
위 원인으로인해 각각 반복문에서의 save는 서로 다른 트랜잭션으로 동작
따라서 i==7일때 exception이 터져도 나머지 다른 경우는 서로 다른 트랜잭션으로 동작하기 때문에 
영향을받지않고 저장된다

@skarltjr
Copy link
Owner Author

skarltjr commented Jan 19, 2023

동일빈 호출문제2

  • 주석 2023-01-19 105951
상황 :
동일빈 호출문제1과 다른점은 오로지 outer()에 @Transactional이 추가된것

내 예상 :
1. outer에서 트랜잭션 시작
2. 그러나 outer에서 this를 통해 inner를 호출하는경우 여전히 동일빈 문제, 즉 내부 호출이 일어나는거고 
3. 그렇기 때문에 inner의 @Transactional은 동작하지 않는다고 생각
4. 그래서 inner에서 예외가 터져도 outer에 영향을 주지 않을거라고 생각

올바른 방향 :
1. 우선 outer에서 시작된 트랜잭션이 inner에 전파되지 않는다.
2. 이유는 그대로 동일빈 호출문제
3. 그런데 여기서 내가 오해한부분이 있다. 
- 내부 호출로 inner의 @Transactional이 동작하지 않는다.
- 그렇기 때문에 새로 트랜잭션을 생성하지 않는다.
- 그런데 트랜잭션 전파란... 새로운 트랜잭션을 생성할때!!! 즉 트랜잭션을 생성하게되는 경우에만
- 이미 존재하는 트랜잭션에 참여할것인지 새로 생성할지등을 결정
- 지금 여기서는 트랜잭션 생성하는 경우가 아니라 이미 존재하는 outer트랜잭션 그냥 사용

7. 그런데.. outer와 inner는 같은 콜스택을 사용
8. 그래서 inner에서 터진 예외가 outer로 넘어가고 outer도 롤백되는것
9. 왜냐? inner outer 같은 트랜잭션 사용하니까
전체 롤백이 일어나는 이유를 조금 더 생각해보자
동일빈 호출문제 1번의 경우 outer에서는 트랜잭션이 시작되지않는다.
그래서 outer의 save가 각기 다른 트랜잭션으로 동작하고 1 저장 반영, 2저장 반영... 
동시에 inner에서도 동일하게 진행 그러다 7에서 예외가 터져도 이미 다른것들은 반영이 된 상태

그런데 동일빈 호출문제2번에서 outer는 트랜잭션이 시작.
그러니 outer.save가 1,2,3 마다 저장 및 반영되는게 아니라 그 동작을 모아뒀다가 한 번에 flush할것
그런데 이때 inner에서 예외가 터지고 같은 콜스택을 사용하기에 outer까지 반영되는데 
동일빈 호출문제 1번과 다르게 지금 outer는 트랜잭션이 시작되어서 1,2,3이 모두 롤백된다
한가지 궁금한점은 그럼 왜 4,5,6이 롤백되는가이다.
여기서 inner는 트랜잭셔널이 동작하지 않아 save가 각기 다른 트랜잭션으로 동작할텐데 
그럼 그 결과를 바로바로 flush해서 4,5,6은 저장되어야한다고 생각하고
최종 결과는 4,5,6만 저장된 상태일것이라고 생각. 
그런데 모두 롤백되어있다. 이 부분만 조금 더 알아봐야할것같다.

궁금증을 해결해보자 - PSA

crudRepository를 impl하는 커스텀 레포를 활용하여 save에서 트랜잭션 이름을 찍어보고자한다.
  • 주석 2023-01-20 132310
이제 getCurrentTxName으로 하나씩 찍어보자
1. outer에서 트랜잭션이 시작되었고 이름은 Myservice.outer다
  • 주석 2023-01-20 132430
2. 커스텀 레포의 save에서 찍은 txName도 MyService.outer다
즉 이말은 일단 outer에서 시작한 tx가 outer 내부에서 실행되는 save까지 전파가 되었다는뜻
  • 주석 2023-01-20 132625
3. inner의 txName 역시 MyService.outer다
즉 이게 중요한데, 
트랜잭션 전파라는것은 결국 트랜잭션을 생성할 수 있는 경우에 이를 판단해야한다.
무슨말이냐면 inner는 outer로 부터 호출되었고 그래서 @Transactional이 동작하지 않아 트랜잭션 생성자체를 하지 않는다.
그러니 전파를 고려하지도 않는다.

결국 inner는 그냥 outer에서 시작된 트랜잭션을 사용한다.
  • 주석 2023-01-20 132934
4. 마지막으로 그럼 inner에서 수행되는 save는 서로 다른빈에서 호출되었으니 @Transactional이 동작하고
이전 트랜잭션에 참여할것. 즉 inner의 save의 txName은 MyService.outer라고 예상
확인해보자
  • 주석 2023-01-20 133105

정리하자면

내가 가장 오해하고 있는 부분은 트랜잭션 전파다. 
outer에서 트랜잭션이 시작했지만 inner는 트랜잭셔널이 동작안해서 전파를 받지 못할거라고 생각.
즉 inner의 트랜잭션은 null일거라 생각

그런데 그게 아니라 트랜잭션 생성 자체를 하지못하니 전파를 고려하지 않고 그냥 이미 시작된 트랜잭션을 사용한다.

그런데

  • 내 생각이 맞다는걸 확인하기 위해선 하나 더 봐야할것같다.
outer와 inner 빈을 분리하고
inner에서 required new로 새로 트랜잭션을 시작
이후 inner의 save 트랜잭션 이름이 inner에서 시작된 트랜잭션이랑은 같고, outer랑은 달라야한다.
확인해보자
1. outer txName : MyService.outer 확인
  • 스크린샷 2023-01-23 오후 9 56 24
2. outer 내부 save는 트랜잭션이 전파되어서 myservice.outer라는 이름을 가질것
  • 스크린샷 2023-01-23 오후 9 57 30
3. 서로 다른빈인 inner는 required_new로 새로운 트랜잭션을 시작하도록
- 여기서 서로다른빈이라 inner의 transactional은 동작할것이고
- 새로운 트랜잭션이 생성될것
- callInner라는 매서드에서 새로운 트랜잭션 시작 확인
  • 스크린샷 2023-01-23 오후 10 01 11
4. 마지막으로 새로운 트랜잭션이 전파되는지 확인
  • 스크린샷 2023-01-23 오후 10 01 53

@skarltjr
Copy link
Owner Author

동일빈 호출문제3

  • 주석 2023-01-19 110733
  • 주석 2023-01-19 110750
상황 : 
동일빈 호출문제는 동일 그런데 이때 outer에서 inner 호출을 try catch로 감싸고있다.
내 예상 :
1. 어차피 outer -> inner로 트랜잭션 전파 x
2. 그럼 일단 inner의 save는 각기 다른 트랜잭션으로 동작
3. exception이 터져도 inner에서 4,5,6은 각기 다른 트랜잭션으로 동작해서 저장될것
4. outer는 예외를 처리했기때문에 같은 콜스택에서 발생한 예외의 영향을 받지않고 저장

@skarltjr
Copy link
Owner Author

분리된 빈 롤백 전파

먼저 순서를 확인하고가자
controller -> myservice.outer() -> myservice2.inner()

!!!! myservice랑 myservice2는 다르다
  • 주석 2023-01-19 111938
  • 주석 2023-01-19 112016
내 예상 : 
1. outer에서 시작된 트랜잭션이 서로 다른빈인 myservice2의 inner까지 전파
2. inner에서 예외가 터졌다
3. outer에서 이를 잡았으니 전체 롤백되지 않고 inner만 롤백될것이라 예상
결과 : 
전체롤백

그 이유는 JDBC transaction marked for rollback-only (exception provided for stack trace)
롤백 마크 처리되어서 outer까지 롤백된다.

@skarltjr
Copy link
Owner Author

분리된 빈 & 트랜잭션 분리

  • 주석 2023-01-19 112635
  • 주석 2023-01-19 112642
상황 : 
outer, inner는 서로 다른 빈의 매서드들
내 예상 : 
1. 일단 outer와 inner는 서로다른빈
2. 그리고 서로 다른 트랜잭션 
3. 그런데 서로 다른빈에서 호출했으니 inner는 독자 트랜잭션 하나로 동작(4,5,6~) 저장 및 롤백이 하나로 동작
4. inner에서 터진 예외를 outer에서 캐치
5. inner는 예외가 터졌으니 전체 롤백
6. outer는 inner와 서로 다른 트랜잭션이며 예외 catch했으니 영향안받고 1,2,3저장
  • 주석 2023-01-19 113059

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant