skarltjr / Memory_Write_Record

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

@Transactional 동작 복기 #141

Open skarltjr opened 1 year ago

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

트랜잭션 전파란
즉, 이미 시작된 트랜잭션이 존재하는 상태에서
!!! 새로운 !!! 트랜잭션이 생성될 때!!! 
그 때
이전 트랜잭션에 참여할것인가,
독자적인 새로운 트랜잭션을 생성할까... 를 고민하는것

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


- ![주석 2023-01-20 103636](https://user-images.githubusercontent.com/62214428/213599384-a27878e3-4cca-41f6-aaf3-d8e75d4b0af1.png)
- ![주석 2023-01-20 103653](https://user-images.githubusercontent.com/62214428/213599398-80adc797-7a05-4ab0-832d-2e77c6ad4329.png)

- 조금 더 분명하게 알기 위한 정리는
- https://github.com/skarltjr/Memory_Write_Record/issues/141#issuecomment-1396348487
skarltjr commented 1 year ago

동일빈 호출문제

    • 주석 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 commented 1 year ago

동일빈 호출문제2

상황 :
동일빈 호출문제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에서 트랜잭션 이름을 찍어보고자한다.
이제 getCurrentTxName으로 하나씩 찍어보자
1. outer에서 트랜잭션이 시작되었고 이름은 Myservice.outer다

결국 inner는 그냥 outer에서 시작된 트랜잭션을 사용한다.

- ![주석 2023-01-20 132934](https://user-images.githubusercontent.com/62214428/213617533-c0eb8362-d17f-4b37-beae-1f9adbf03d2b.png)
  1. 마지막으로 그럼 inner에서 수행되는 save는 서로 다른빈에서 호출되었으니 @Transactional이 동작하고 이전 트랜잭션에 참여할것. 즉 inner의 save의 txName은 MyService.outer라고 예상 확인해보자
    
    - ![주석 2023-01-20 133105](https://user-images.githubusercontent.com/62214428/213617685-598dd952-9b8a-4e69-b86e-ae0ab6a0fe9f.png)

정리하자면

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

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

그런데

2. outer 내부 save는 트랜잭션이 전파되어서 myservice.outer라는 이름을 가질것
skarltjr commented 1 year ago

동일빈 호출문제3

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

분리된 빈 롤백 전파

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

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

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

분리된 빈 & 트랜잭션 분리