@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
inner
앞에서는 outer와 inner가 같은 트랜잭션이었고
inner에서 발생한 예외를 outer에서 잡았지만 결국 같은 트랜잭션내에서 runtime exception이 발생해서
이를 rollback check한 뒤 outer까지 롤백
이 경우는 outer와 inner는 서로다른 트랜잭션
inner에서 발생한 예외를 outer에서 잡았고
서로 다른 트랜잭션이기때문에 inner는 롤백,
outer는 이에 영향을 받지 않는다
들어가기 앞서
TransactionAspectSupport.invokeWithinTransaction()
OuterService
InnerService
왜?
outer
inner
이 경우는 outer와 inner는 서로다른 트랜잭션 inner에서 발생한 예외를 outer에서 잡았고 서로 다른 트랜잭션이기때문에 inner는 롤백, outer는 이에 영향을 받지 않는다