JAVA/JAVA | Spring 학습기록

[Spring] REQUIRED, REQUIRES_NEW 옵션과 Try-Catch

kth990303 2022. 10. 25. 23:12
반응형

스프링에서 트랜잭션을 다루다보면 두 개 이상의 트랜잭션이 서로 합류하는 경우가 존재한다. 이 때, @Transactional의 전파레벨을 적절히 조정하는 것이 중요하다. 스프링 전파레벨에 대한 이해가 부족하다면 아래 글을 읽고 오는 것을 추천한다.

 

 

지난 포스팅(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/

 

응? 이게 왜 롤백되는거지? | 우아한형제들 기술블로그

{{item.name}} 이 글은 얼마 전 에러로그 하나에 대한 호기심과 의문으로 시작해서 스프링의 트랜잭션 내에서 예외가 어떻게 처리되는지를 이해하기 위해 삽질을 해본 경험을 토대로 쓰여졌습니다.

techblog.woowahan.com


자식 트랜잭션이 REQUIRED (default) 전파레벨일 때, 자식 try-catch

부모는 REQUIRED, 자식도 REQUIRED_NEW 상황이다.

자식에서 RuntimeException이 발생했지만 try-catch로 감쌌을 때의 결과는 위와 같다.

 

자식에서 try-catch로 감싸주었기 때문에 @Transactional 프록시 입장에서는 예외 발생을 알 수 없어 rollback시키지 않고 커밋하게 된다. 당연히 부모도 커밋하게 된다.


참고

 

도움을 준 사람

  • 우아한테크코스 4기 BE 차리

 

반응형