이미 진행 중인 트랜잭션이 있을 때 새로운 트랜잭션을 어떻게 처리할지에 대한 전략이다.
스프링 프레임워크는 외부 트랜잭션이 존재하는 상황에서 내부 트랜잭션을 호출하면, 내부 트랜잭션은 외부 트랜잭션에 참여한다.
물리 트랜잭션(Physical Transaction): 데이터베이스와의 연결에서 시작되고 커밋 또는 롤백으로 끝나는 실제 트랜잭션이다.
하나의 커넥션에서 하나의 트랜잭션만 관리된다.
논리 트랜잭션(Logical Transaction): 스프링과 같은 프레임워크가 관리하는 트랜잭션 경계이다.
논리 트랜잭션은 여러 메서드 호출을 감싸며 동작한다.
@Transactional
public void outerMethod() {
// 트랜잭션 시작 (물리 트랜잭션 생성)
innerMethod();
// 비즈니스 로직
// outerMethod가 종료되며 트랜잭션 커밋
}
@Transactional
public void innerMethod() {
// 이미 진행 중인 트랜잭션에 참여
// 비즈니스 로직
}
outerMethod()가 실행되며 트랜잭션이 시작되고, 물리 트랜잭션이 생성된다.
innerMethod()가 호출되면서 별도의 물리 트랜잭션을 생성하지 않고 이미 시작된 외부 트랜잭션에 참여한다.
논리적으로 innerMethod가 끝났지만, 커밋은 실제로 일어나지 않는다. 커밋은 outerMethod가 끝날 때 한 번만 발생한다.
내부적으로 innerMethod가 오류 없이 끝나야 논리적으로 커밋 가능 상태가 된다.
물리 트랜잭션의 커밋은 외부 트랜잭션이 끝날 때 딱 한 번 발생한다.
외부 트랜잭션이 롤백되면 전체 트랜잭션이 롤백된다.
내부 트랜잭션이 커밋되었더라도 물리 트랜잭션에는 반영되지 않으며, 물리 트랜잭션이 롤백되면 전체 트랜잭션이 롤백된다.
내부 트랜잭션이 롤백되면, 물리 트랜잭션을 즉시 롤백하지 않는다.
대신, 롤백 전용(rollbackOnly=true) 으로 표시하여 이후 외부 트랜잭션이 커밋을 시도할 경우 롤백이 발생한다.
스프링 로그 메시지 예시:
participating transaction failed - marking existing transaction as rollback-only
외부 트랜잭션이 커밋을 시도하면 다음과 같은 예외가 발생하며 물리 트랜잭션이 롤백된다.
global transaction is marked as rollback-only but transactional code requested commit
트랜잭션 동기화 매니저가 물리 롤백을 실행하고, 트랜잭션 매니저가 UnexpectedRollbackException을 던진다.
내부 트랜잭션이 외부 트랜잭션과 분리된 별도의 물리 트랜잭션을 사용하려면 REQUIRES_NEW 옵션을 사용한다.
REQUIRES_NEW 옵션을 적용하면 내부 트랜잭션은 새로운 물리 connection을 사용하여 독립적으로 처리된다.
즉, 새로운 데이터베이스 커넥션을 별도로 생성하여 독립적인 트랜잭션을 수행한다.
이를 통해 외부 트랜잭션이 롤백되더라도 내부 트랜잭션이 영향을 받지 않도록 할 수 있다.