스프링에서 트랜잭션을 다루다보면 두 개 이상의 트랜잭션이 서로 합류하는 경우가 존재한다. 이 때, @Transactional의 전파레벨을 적절히 조정하는 것이 중요하다. 스프링 전파레벨에 대한 이해가 부족하다면 아래 글을 읽고 오는 것을 추천한다.
- 스프링 @Transactional 전파레벨: https://kth990303.tistory.com/385
- @Transactional REQUIRES_NEW 롤백 상황: https://kth990303.tistory.com/387
지난 포스팅(387번)에서는 REQUIRES_NEW 전파레벨 옵션일 때 @TransactionalEventListener를 이용하여 자식이 롤백되더라도 부모가 롤백되지 않도록 설정해주었다.
이번 포스팅에서는 try-catch에 대해 살펴보겠다.
트랜잭션 합류와 무관하게 단순히 try-catch의 경우
트랜잭션 합류 및 전파레벨 상황에서 try-catch를 살펴보기 전에,
아래처럼 하나의 트랜잭션에서 예외가 발생했을 때 try-catch로 감싸준 상황을 먼저 보자.
user를 저장하고 예외를 발생시켰다. 하지만 try-catch로 잡아주었다.
이런 경우에 user 저장은 커밋될까? 롤백될까?
정답은 커밋된다! 이다.
try-catch로 예외를 감싸주었기 때문에 @Transactional의 프록시 입장에서는 해당 메서드 진행에 문제없이 잘 진행됐으므로 예외 플래그를 따로 true로 바꾸지 않는다.
이제 두 개의 트랜잭션이 합류하는 상황을 살펴보자.
자식 트랜잭션이 REQUIRES_NEW 전파레벨일 때, 자식 try-catch
먼저 부모는 REQUIRED, 자식은 REQUIRES_NEW 상황이다.
자식에서 RuntimeException이 발생했지만 try-catch로 감쌌을 때의 결과는 위와 같다.
(위 사진에서 @Displayname을 읽는 것을 추천한다.)
위의 이유와 마찬가지로 자식에서 try-catch로 감싸주었기 때문에 @Transactional 프록시 입장에서는 예외 발생을 알 수 없어 rollback시키지 않고 자식 트랜잭션을 커밋하게 된다. 부모 또한 커밋된다.
참고로 위 그림에서 진행된 메서드인 firstUserService.saveFirstTransactionWithRequiredNewBabyTryCatch 메서드는 아래와 같다.
부모 트랜잭션에서 user1 저장 -> 자식 트랜잭션에서 user2 저장 후 RuntimeException -> 부모 트랜잭션에서 user3 저장
자식 트랜잭션 실행(예외 - try-catch): user2 save
자식 트랜잭션이 REQUIRES_NEW 전파레벨일 때, 부모 try-catch
부모는 REQUIRED, 자식은 REQUIRES_NEW 상황이다.
자식에서 RuntimeException이 발생했고, try-catch로 감싸지 않았다. 대신 부모에서 try-catch로 감싸주었다.
결과는 위와 같다. (부모 트랜잭션 커밋, 자식 트랜잭션 롤백돼서 사이즈 1. @Displayname을 읽는 것을 추천한다.)
자식에서 RuntimeException()이 발생하게 되면 자식 트랜잭션이 끝날 때 예외가 발생한 상태이므로 예외 플래그를 true로 하게 된다. 그렇기 때문에 자식트랜잭션은 rollback 되게 된다.
자식이 REQUIRES_NEW이기 때문에 부모는 별개의 트랜잭션이다. 만약 부모에도 try-catch가 걸려있지 않았다면 부모도 RuntimeException()을 그대로 받아서 롤백돼버렸겠지만, 부모에 try-catch로 감싸져있기 때문에 @Transactional 프록시 입장에서는 예외 발생을 알 길이 없어 예외플래그를 true로 바꾸지 않는다. 따라서 부모 트랜잭션은 커밋한다. (단, 자식 트랜잭션 rollback 이후에 try 블록 내부에 save하는 메서드는 아예 실행되지 않으니 주의하자.)
(조금 어렵지만 중요하므로 잘 읽도록 하자!)
참고로 위 테스트 코드에서 firstUserService.saveFirstTransactionWithRequiredNewParentTryCatch()는 아래와 같다.
`user1 save -> 자식 트랜잭션 -> user3 save` 하려 했지만, 자식 트랜잭션에서 예외 발생해서 user3 save 로직은 실행조차 되지 않고 user1만 커밋된다.
자식 트랜잭션 실행(예외 - try-catch X): user2 save하려 했지만 rollback
자식 트랜잭션이 REQUIRED (default) 전파레벨일 때, 부모 try-catch
위의 REQUIRES_NEW 상황을 잘 이해했다면 아래 REQUIRED 전파레벨 옵션에선 어렵지 않다.
먼저 부모는 REQUIRED, 자식도 REQUIRED 상황이다.
자식에서 RuntimeException이 발생했고, try-catch로 감싸지 않았다. 대신 부모에서 try-catch로 감싸주었다.
결과는 위와 같다.
자식에서 RuntimeException()이 발생하게 되면 자식 트랜잭션이 끝날 때 예외가 발생한 상태이므로 예외 플래그를 true로 하게 된다. 그렇기 때문에 자식트랜잭션의 user2는 rollback 되게 된다.
그리고 자식트랜잭션 전파레벨 옵션이 default인 REQUIRED이므로 부모 트랜잭션과 동일한 트랜잭션이므로 당연히 부모도 롤백된다.
참고로 이 경우는 정확히 아래 글에 해당되는 경우와 일치한다.
https://techblog.woowahan.com/2606/
자식 트랜잭션이 REQUIRED (default) 전파레벨일 때, 자식 try-catch
부모는 REQUIRED, 자식도 REQUIRED_NEW 상황이다.
자식에서 RuntimeException이 발생했지만 try-catch로 감쌌을 때의 결과는 위와 같다.
자식에서 try-catch로 감싸주었기 때문에 @Transactional 프록시 입장에서는 예외 발생을 알 수 없어 rollback시키지 않고 커밋하게 된다. 당연히 부모도 커밋하게 된다.
참고
도움을 준 사람
- 우아한테크코스 4기 BE 차리
'JAVA > JAVA | Spring 학습기록' 카테고리의 다른 글
[Spring] MapStruct를 이용한 Entity <-> DTO 고찰 (2) (7) | 2022.12.10 |
---|---|
[Spring] @Configuration vs @Component (2) | 2022.11.24 |
[Spring] REQUIRES_NEW 옵션만으론 자식이 롤백될 때 부모도 롤백된다 (2) | 2022.10.20 |
[Spring] @Transactional의 전파 레벨에 대해 알아보자 (4) | 2022.10.17 |
[Spring] @SpringBootTest의 webEnvironment와 @Transactional (5) | 2022.08.23 |