들어가며
웹 애플리케이션을 개발할 때, 흔히 비즈니스 로직이 시작되는 서비스 계층에 @Transactional 어노테이션을 적용해주어 트랜잭션의 경계를 설정하곤 한다. 좀 더 구체적으로는 보통 공통적으로 적용할 트랜잭션 속성을 @Transactional과 함께 작성하여 클래스 레벨에 부여하고, 보다 세밀하게 트랜잭션 속성을 적용해야 할 경우라면 메서드 레벨에 @Transactional을 부여한다.
그렇다면 @Transactional 어노테이션은 매번 새로운 트랜잭션을 생성하는 것일까?
이러한 의문이 들었을 때 트랜잭션 전파에 대해 정리해볼 필요가 있다고 느꼈고, 이에 대해 알아보려고 한다.
트랜잭션 전파
트랜잭션 전파는 트랜잭션의 경계에서 이미 진행 중인 트랜잭션이 있을 때, 또는 없을 때 어떻게 동작할 것인가를 결정하는 방식으로, 스프링에서는 @Transactional 어노테이션의 propagation 속성으로 이를 지정할 수 있다. 즉, 트랜잭션 경계의 시작 지점에서 해당 범위의 트랜잭션을 어떻게 진행시킬지 결정할 수 있다는 것이다.
이제 이러한 트랜잭션 전파 방법을 결정하는 속성에는 어떤 것들이 있는지 알아보도록 하자.
REQUIRED
디폴트 속성으로, 미리 시작된 트랜잭션이 있으면 참여하고, 그렇지 않으면 새로 시작한다. 즉, 하나의 트랜잭션이 시작된 후에 다른 트랜잭션 경계가 설정된 메소드를 호출하면 자연스럽게 같은 트랜잭션으로 묶인다.
예를 들어, 위 그림과 같이 기능 A와 기능 B에 모두 트랜잭션 경계가 설정되어 있고 전파 속성이 REQUIRED라면, 기능 B는 기능 A에서 시작된 트랜잭션에 묶이게 된다. 이로 인해 기능 A나 기능 B를 수행하다가 롤백이 발생하게 되면 모든 과정이 롤백되게 된다.
MANDATORY
REQUIRED와 비슷하게 이미 시작된 트랜잭션이 있으면 참여한다. 하지만 만약 트랜잭션이 시작된 것이 없다면 새로 시작하는 대신 예외를 발생시킨다. 혼자서는 독립적으로 트랜잭션을 진행하면 안 되는 경우에 사용한다.
REQUIRES_NEW
항상 새로운 트랜잭션을 시작하고, 이미 진행 중인 트랜잭션이 있으면 트랜잭션을 잠시 보류시킨다.
예를 들어, 위 그림과 같이 기능 A는 REQUIRED 전파 속성을 가지고 있고 기능 B는 REQUIRES_NEW 전파 속성을 가지고 있다면, 기능 B는 기능 A에서 시작된 트랜잭션과 별개로 새로운 트랜잭션을 생성한다. 이로 인해 기능 A에서 시작된 트랜잭션과 기능 B에서 시작된 트랜잭션은 서로에게 영향을 미치지 않으며, 기능 B가 수행 중에 롤백되더라도 기능 A는 롤백되지 않는다. 그 반대의 경우도 마찬가지이다.
NESTED
이미 진행 중인 트랜잭션이 있으면 중첩 트랜잭션을 시작하고, 그렇지 않으면 새로운 트랜잭션을 시작한다. 중첩 트랜잭션은 트랜잭션 안에 다시 트랜잭션을 만드는 것인데, 독립적인 트랜잭션을 만드는 REQUIRES_NEW와는 다르다.
중첩된 트랜잭션은 먼저 시작된 부모 트랜잭션의 커밋과 롤백에 영향을 받지만, 자신의 커밋과 롤백은 부모 트랜잭션에 영향을 주지 않는다.
예를 들어, 위 그림과 같이 기능 A는 REQUIRED 전파 속성을 가지고 있고 기능 B는 NESTED 전파 속성을 가지고 있다면, 기능 B는 중첩 트랜잭션을 생성한다. 이러한 기능 B를 수행하던 도중 롤백이 발생하더라도 중첩 트랜잭션은 부모 트랜잭션에 영향을 주지 않기 때문에 기능 A는 정상적으로 커밋될 수 있다. 하지만 반대로 기능 A를 수행하다가 롤백이 발생하게 되면 중첩 트랜잭션은 부모 트랜잭션의 영향을 받기 때문에 정상적으로 수행이 완료된 기능 B도 롤백되게 된다.
NEVER
트랜잭션을 사용하지 않도록 강제하며, 이미 진행 중인 트랜잭션이 존재할 경우 예외를 발생시킨다.
SUPPORTS
이미 시작된 트랜잭션이 있으면 참여하고, 그렇지 않으면 트랜잭션 없이 진행한다.
NOT_SUPPORTED
트랜잭션을 사용하지 않는 속성으로, 이미 진행 중인 트랜잭션이 있으면 보류시킨다.
마치며
트랜잭션의 전파 속성에 따라 어떤 방식으로 전파가 이루어지는지에 대한 감을 잡을 수 있었던 좋은 시간이 된 것 같다. 이를 기반으로 좀 더 학습한 뒤, 각각의 전파 속성이 어떤 경우에 활용될 수 있을지에 대해 고민해보도록 하자.
Reference
'Backend > Spring' 카테고리의 다른 글
Redis를 활용한 캐싱 적용기 (0) | 2022.05.20 |
---|---|
테스트 격리하기 (0) | 2022.05.04 |
AOP와 빈 후처리기를 이용한 부가 기능 분리 (0) | 2022.03.26 |
다이내믹 프록시를 활용한 JPA QueryCounter 구현기 (0) | 2022.03.20 |
다이내믹 프록시를 이용한 부가 기능 분리 (0) | 2022.03.12 |