트랜잭션 전파시 ROLLBACK이 발생되면 어떻게 될까?
이번글은 외부 트랜잭션, 내부 트랜잭션에서 롤백이 발생하는 경우와 코드 예제를 통해 흐름을 알아보겠습니다.
트랜잭션 전파 옵션은 여러개가 있지만, REQUIRED를 기준으로 했고, 김영한님의 Spring DB V2 강의를 참고해서 작성했습니다.
내부 트랜잭션이 커밋되고 외부 트랜잭션이 롤백되면 어떻게 될까요?
내부 트랜잭션 커밋 & 외부 트랜잭션 롤백
내부 트랜잭션을 커밋해도, 외부 트랜잭션이 롤백되면 내부 트랜잭션의 데이터도 전부 롤백됩니다.
예시 코드로 확인해보겠습니다.
@Test
void outer_transaction_rollback() {
log.info("outer 트랜잭션 시작");
TransactionStatus outerStatus = tx.getTransaction(new DefaultTransactionAttribute());
log.info("inner 트랜잭션 시작");
TransactionStatus innerStatus = tx.getTransaction(new DefaultTransactionAttribute());
log.info("inner 트랜잭션 커밋");
tx.commit(innerStatus);
log.info("outer 트랜잭션 롤백");
tx.rollback(outerStatus);
}
실행결과
내부 트랜잭션은 아무것도 진행이 되지 않고, 외부 트랜잭션의 롤백만 적용된 것을 확인할 수 있습니다.
이 흐름을 그림으로 보면 다음과 같습니다.
요청 흐름은 트랜잭션 전파(REQUIRED) COMMIT편에 작성한 부분과 일치하므로, 응답 흐름만 보겠습니다.
먼저 내부 트랙잭션의 응답 흐름은 다음과 같습니다.
1. 로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 커밋합니다.
2. 내부 트랜잭션 커밋이 발생해도 신규 트랜잭션이 아니기 때문에 실제 커밋을 호출하지 않습니다.
외부 트랜잭션의 응답 흐름은 다음과 같습니다.
1. 로직1이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 롤백합니다.
2. 외부 트랜잭션은 신규 트랜잭션이므로 DB 커넥션에 실제 롤백을 호출합니다.
3. 실제 데이터베이스에 롤백이 반영되고, 물리 트랜잭션이 끝납니다.
만약, 외부에서 롤백이 발생하고, 내부에서 트랜잭션 커밋이 되면 어떻게 될까요?
내부 트랜잭션 롤백 & 외부 트랜잭션 커밋
내부 트랜잭션은 물리 트랜잭션에 영향을 주지 않기 때문에, 롤백을 해도 롤백이 적용되지 않은 상태에서 외부 트랜잭션이 커밋을 하게 됩니다.
예시 코드로 확인해보겠습니다.
@Test
void inner_transaction_rollback() {
log.info("outer 트랜잭션 시작");
TransactionStatus outerStatus = tx.getTransaction(new DefaultTransactionAttribute());
log.info("inner 트랜잭션 시작");
TransactionStatus innerStatus = tx.getTransaction(new DefaultTransactionAttribute());
log.info("inner 트랜잭션 롤백");
tx.rollback(innerStatus);
log.info("outer 트랜잭션 커밋");
tx.commit(outerStatus);
}
실행 결과
"Participating transaction failed - marking existing transaction as rollback-only"라는 로그가 보입니다.
직역하면 "참여 트랜잭션 실패 - 기존 트랜잭션을 롤백 전용으로 표시"입니다.
"Setting JDBC transaction [HikariProxyConnection@219665748 wrapping conn0: url=jdbc:h2:tcp://localhost/./testcase user=SA] rollback-only" 라는 로그가 보입니다. (위 이미지는 캡쳐해서 일부가 잘려있습니다.)
롤백 전용(rollback-only) 상태로 설정합니다.
다음 로그는 "Global transaction is marked as rollback-only but transactional code requested commit"라는 로그 입니다.
직역하면, "글로벌 트랜잭션은 롤백 전용으로 표시되지만 트랜잭션 코드는 커밋을 요청합니다."입니다.
내부 트랜잭션이 롤백요청을 해서 물리 트랜잭션이 rollback-only로 되었지만, 외부 트랜잭션이 commit요청을해서 생기는 로그입니다.
다음 로그는 "Rolling back JDBC transaction on Connection [HikariProxyConnection@219665748 wrapping conn0: url=jdbc:h2:tcp://localhost/./testcase user=SA]" 라는 로그가 보입니다. (위 이미지는 캡쳐해서 일부가 잘려있습니다.)
외부 트랜잭션이 커밋을 했음에도 불구하고, 롤백이 실행됩니다.
흐름을 정리하면 다음과 같습니다.
1. 외부 트랜잭션 시작으로 물리 트랜잭션이 시작됩니다.
2. 내부 트랜잭션이 시작됩니다.
3. 내부 트랜잭션에서 롤백이 호출됩니다. 실제 롤백이 호출되지 않고, 외부 트랜잭션(물리 트랜잭션)을 rollback-only로 표시합니다.
4. 외부 트랜잭션에서 커밋이 호출됩니다. 커밋을 호출함에도 불구하고, 전체 트랜잭션(물리 트랜잭션)이 rollback-only로 표시되어 있어 실제 롤백이 진행됩니다.
이 흐름(응답 기준)을 그림으로 보면 다음과 같습니다.
요청 흐름은 트랜잭션 전파(REQUIRED) COMMIT편에 작성한 부분과 일치하므로, 응답 흐름만 보겠습니다.
먼저 내부 트랙잭션의 응답 흐름은 다음과 같습니다.
1. 로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 롤백합니다.
2. 내부 트랜잭션은 물리 트랜잭션을 롤백하지 않는 대신에 트랜잭션 동기화 매니저에 rollbackOnly = true 표시를 합니다.
외부 트랜잭션의 응답 흐름은 다음과 같습니다.
- 로직1이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋합니다.
- 트랜잭션 동기화 매니저에 롤백 전용(rollback-Only=true) 표시 여부를 확인합니다. 롤백 전용 표시가 있으므로 물리 트랜잭션을 커밋하지 않고, 롤백합니다.
- 실제 DB에 롤백이 반영되고, 물리 트랜잭션이 종료됩니다.
스프링은 이러한 경우 아래와 같은 에러를 던집니다.
![](https://blog.kakaocdn.net/dn/KfT4F/btsMhb1oWHS/py2mA5ft3A34xWZWRKKLP0/img.png)
정리
- 논리 트랜잭션이 하나라도 롤백되면 물리 트랜잭션은 롤백됩니다.
- 내부 논리 트랜잭션이 롤백되면 롤백 전용 마크를 표시합니다.
- 외부 트랜잭션을 커밋할 때 롤백 전용 마크를 확인합니다.
- 롤백 전용 마크가 표시되어 있으면 물리 트랜잭션을 롤백 하고, UnexpectedRollbackException 예외를 던집니다.
'Spring' 카테고리의 다른 글
트랜잭션 전파(REQUIRED) COMMIT편 (0) | 2025.02.12 |
---|---|
트랜잭션과 Connection (0) | 2025.02.11 |