트랜잭션 전파
@Transcational 어노테이션의 기본 옵션은 REQUIRED이다.
public class A {
@Transcational
public void A() {
log.info("A 외부 트랜잭션 시작");
B();
}
@Transcational
public void B() {
log.info("B 내부 트랜잭션 시작");
}
}
위와 같은 상황에서 A 메서드에서의 트랜잭션을 외부 트랜잭션 B를 내부 트랜잭션이라 말할 수 있다.
스프링의 경우 외부 트랜잭션과 내부 트랜잭션을 묶어서 하나의 트랜잭션을 만들어준다. 내부 트랜잭션이 외부 트랜잭션에 참여하는것이다. 이것이 기본 옵션은 REQUIRED일때의 트랜잭션 방식이다.
물리 트랜잭션의 실제 DB에서의 트랜잭션이고 논리 트랜잭션은 트랜잭션 매니저를 통한 트랜잭션을 사용하는 단위이다.
논리 트랜잭션은 하나의 물리 트랜잭션으로 묶인다.
이러한 논리 트랜잭션 개념은 트랜잭션이 진행중에 내부에 추가로 트랜잭션을 사용하는 경우에 나타난다.
위에서 설명했듯이 논리 트랜잭션들은 하나의 물리 트랜잭션으로 묶여있습니다.
그러므로 모든 논리 트랜잭션이 커밋이 되어야 물리 트랜잭션이 커밋됩니다.
public class A {
@Transcational
public void A() {
log.info("A 외부 트랜잭션 시작");
B();
}
@Transcational
public void B() {
log.info("B 내부 트랜잭션 시작");
//rollback 로직
}
}
위와 같이 내부 트랜잭션중에 롤백이 발생할만한 로직이 있으면 스프링 트랜잭션은 내부적으로 rollbackOnly=true
마킹을 하고 A트랜잭션을 커밋시점에 예외가 발생합니다. 그래서 A, B 트랜잭션 모두 롤백이 되고 맙니다.
기본 옵션 REQUIRED에서는 기존 트랜잭션에 다른 트랜잭션도 참여하기 때문에 외부 롤백도 같은 흐름으로 진행됩니다.
이제 외부 트랜잭션과 내부 트랜잭션을 다른 트랜잭션으로 사용할 수 있는 방법에 대해 알아보겠습니다.
별도의 트랜잭션을 가진다는 뜻은 DB커넥션을 따로 사용한다는 뜻이기도 합니다.
이렇게 되면 외부 트랜잭션과 내부트랜잭션이 가각 별도의 물리 트랜잭션을 가지게 됩니다.
public class A {
@Transcational
public void A() {
log.info("A 외부 트랜잭션 시작");
B();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void B() {
log.info("B 내부 트랜잭션 시작");
//rollback 로직
}
}
위와 같이 Propagation.REQUIRES_NEW를 사용하게 되면 새로운 커넥션 풀을 얻어와 새로운 물리 트랜잭션으로 사용하게 됩니다.
그래서 A 외부 트랜잭션은 B 내부트랜잭션에서의 롤백과 상관없이 커밋되게 됩니다.
이외의 트랜잭션의 전파 옵션은 다음과 같습니다.
REQUIRED
가장 많이 사용하는 기본 설정이다. 기존 트랜잭션이 없으면 생성하고, 있으면 참여한다. 트랜잭션이 필수라는 의미로 이해하면 된다. (필수이기 때문에 없으면 만들고, 있으면 참여한다.) 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.
REQUIRES_NEW
항상 새로운 트랜잭션을 생성한다.
기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다. 기존 트랜잭션 있음: 새로운 트랜잭션을 생성한다.
SUPPORT
트랜잭션을 지원한다는 뜻이다. 기존 트랜잭션이 없으면, 없는대로 진행하고, 있으면 참여한다. 기존 트랜잭션 없음: 트랜잭션 없이 진행한다.
기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.
NOT_SUPPORT
트랜잭션을 지원하지 않는다는 의미이다.
기존 트랜잭션 없음: 트랜잭션 없이 진행한다.
기존 트랜잭션 있음: 트랜잭션 없이 진행한다. (기존 트랜잭션은 보류한다)
MANDATORY
의무사항이다. 트랜잭션이 반드시 있어야 한다. 기존 트랜잭션이 없으면 예외가 발생한다. 기존 트랜잭션 없음: IllegalTransactionStateException 예외 발생
기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.
NEVER
트랜잭션을 사용하지 않는다는 의미이다. 기존 트랜잭션이 있으면 예외가 발생한다. 기존 트랜잭션도 허용하지 않는 강한 부정의 의미로 이해하면 된다.
기존 트랜잭션 없음: 트랜잭션 없이 진행한다.
기존 트랜잭션 있음: IllegalTransactionStateException 예외 발생
NESTED
- 기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
- 기존 트랜잭션 있음: 중첩 트랜잭션을 만든다.
- 중첩 트랜잭션은 외부 트랜잭션의 영향을 받지만, 중첩 트랜잭션은 외부에 영향을 주지 않는다. 중첩 트랜잭션이 롤백 되어도 외부 트랜잭션은 커밋할 수 있다.
- 외부 트랜잭션이 롤백 되면 중첩 트랜잭션도 함께 롤백된다.
참고
JDBC savepoint 기능을 사용한다. DB 드라이버에서 해당 기능을 지원하는지 확인이 필요하다.
중첩 트랜잭션은 JPA에서는 사용할 수 없다.
트랜잭션 전파와 옵션
isolation, timeout, readOnly는 트랜잭션이 처음 시작될 때만 사용된다. 트랜잭션에 참여하는 경우에는 적용되지 않는다.
예를 들어서 REQUIRED를 통한 트랜잭션 시작, REQUIRES_NEW를 통한 트랜잭션 시작 시점에만 적용된다.
@Transactional을 사용할 때 주의할 점은?
- @Transactional 어노테이션이 붙은 메서드는 트랜잭션 처리를 위해 Proxy 객체를 생성하는데 Proxy는 Target Class를 상속하여 생성된다. 따라서 상속이 불가능한 Private 메서드의 경우 @Transactional 어노테이션이 적용할 수 없다는 것을 주의해야 한다.
- Proxy 객체의 Target Method가 내부 메서드를 호출하면 실제 메서드가 호출되기 때문에 InnerMethod에서 @Transactional 어노테이션이 적용되지 않는 것을 주의해야 한다.
- 예를 들어 A 메서드의 시작이 @Transactional 없이 시작했다면 A메서드 안에서 @Transactional 어노테이션이 붙은 B메서드를 호출한다면 트랜잭션이 적용되지 않는다. 이 것을 해결하기 위해서는 새로운 클래스를 만들고 그 클래스에서 B메서드를 호출해야 한다.
- 스프링에서는 UncheckedException(RuntimeException) 은 롤백처리를 하고 checkedException(Excetpion)은 커밋을 한다.
댓글