트랜잭션 매니저는 동작 시점에 신규 트랜잭션 여부에 따라 동작한다.
논리 트랜잭션 하나라도 롤백이 발생하면 전체 트랜잭션이 롤백된다.
@Test
void outer_rollback() {
log.info("외부 트랜잭션 시작");
TransactionStatus outer = txManager.getTransaction(new DefaultTransactionDefinition());
log.info("outer.isNewTransaction = {}", outer.isNewTransaction());
log.info("내부 트랜잭션 시작");
TransactionStatus inner = txManager.getTransaction(new DefaultTransactionDefinition());
log.info("inner.isNewTransaction = {}", inner.isNewTransaction());
log.info("내부 트랜잭션 커밋");
txManager.commit(inner);
log.info("외부 트랜잭션 롤백");
txManager.rollback(outer);
}
내부 트랜잭션은 앞서 배운대로 직접 물리 트랜잭션에 관여하지 않는다.
결과적으로 외부 트랜잭션에서 시작한 물리 트랜잭션의 범위가 내부 트랜잭션까지 사용되고, 외부 트랜잭션이 롤백되면서 전체 내용은 모두 롤백된다.
내부 트랜잭션은 롤백을 했지만 외부 트랜잭션이 커밋을 했을 떄의 상황은 복잡하다.
외부 트랜잭션에 의해서만 물리 트랜잭션이 동작하기 때문에 커밋될 것 처럼 보인다.
@Test
void inner_rollback() {
log.info("외부 트랜잭션 시작");
TransactionStatus outer = txManager.getTransaction(new DefaultTransactionDefinition());
log.info("outer.isNewTransaction = {}", outer.isNewTransaction());
log.info("내부 트랜잭션 시작");
TransactionStatus inner = txManager.getTransaction(new DefaultTransactionDefinition());
log.info("inner.isNewTransaction = {}", inner.isNewTransaction());
log.info("내부 트랜잭션 롤백");
txManager.rollback(inner);
log.info("외부 트랜잭션 커밋");
txManager.commit(outer);
}
내부 트랜잭션 시작
Participating in existing transaction
inner.isNewTransaction = false
내부 트랜잭션 롤백
Participating transaction failed - marking existing transaction as rollback-only
Setting JDBC transaction [HikariProxyConnection@595382884 wrapping conn0:[...] rollback-only
외부 트랜잭션 커밋
Global transaction is marked as rollback-only but transactional code requested commit
Initiating transaction rollback
Rolling back JDBC transaction on Connection [HikariProxyConnection@595382884 wrapping conn0: ...]
Releasing JDBC Connection [HikariProxyConnection@595382884 wrapping conn0:[...]
내부 트랜잭션이 롤백되면 현재 참여중인 트랜잭션에 rollback-only
마크를 한다는 로그가 출력된다.
외부 트랜잭션을 커밋하면 글로벌 트랜잭션은 rollback-only라고 마크되어있는데도 commit을 요청했다라는 로그가 출력되고 트랜잭션 매니저는 롤백을 수행한다.
내부 트랜잭션을 롤백하면 실제 물리 트랜잭션은 롤백하지 못하기 때문에 트랜잭션 동기화 매니저에 롤백 전용으로 표시한다.
개발자는 커밋을 기대했는데 롤백이 수행되었다.
시스템은 이 사실을 에러를 통해 개발자에게 알려줘야 하기때문에 UnexpectedRollbakException
런타임 예외를 던진다.
(커밋을 시도했지만 기대하지 않은 롤백이 발생했다는 의미)