트랜잭션이 중첩되게 되는 경우, 기본값으로는
REQUIRED
로 기존 트랜잭션에 참여하게 된다. 이때, 생성된 신규 트랜잭션은물리 트랜잭션
, 참여한 트랜잭션을 (실제로는 nothing)논리 트랜잭션
이라고 칭하게 된다.
트랜잭션 중첩 상황에서 대원칙은
하나의 논리 트랜잭션이라도 롤백되면, 물리 트랜잭션은 롤백된다.
이다.
논리 트랜잭션에서 예외가 발생했는데, 이를 상위의 논리 트랜잭션에서 catch해서 처리한 경우는 물리 트랜잭션이 커밋하게 될까??
@Transactional
public Member joinV2(String username) {
Member member = new Member(username);
Log logMessage = new Log(username);
log.info("=== memberRepository 호출 시작 ===");
memberRepository.save(member);
log.info("=== memberRepository 호출 종료 ===");
try {
log.info("=== logRepository 호출 시작 ===");
logRepository.save(logMessage);
log.info("=== logRepository 호출 종료 ===");
} catch (MyException me) {
log.info("log 저장 시 예외 발생");
log.info("정상 흐름 반환");
}
return member;
}
정답은 롤백
하게 된다.
그 이유는 논리 트랜잭션이 롤백하게 될 경우, 트랜잭션 매니저는 실제 물리 트랜잭션을 롤백하는 것이 아니라, 트랜잭션 정보를 rollback-only
로 수정한다.
...실제 트랜잭션 log...
[Test worker] o.s.orm.jpa.JpaTransactionManager :
Participating transaction failed - marking existing transaction as rollback-only
따라서 물리 트랜잭션 입장에서는 하위 트랜잭션에서 발생한 예외를 처리했기에, 커밋을 수행하게 된다. 그러나 트랜잭션 매니저는 이 트랜잭션이 rollback-only 설정이 돼있기에, UnexpectedRollbackException
을 던지게 된다.
그렇다면 트랜잭션 매니저가 트랜잭션 정보를 rollback-only로 수정하게 되는 조건이 무엇일까?
논리 트랜잭션 내부에서 처리되지 않은 런타임 예외가 있을 때
이다.
두 상황을 비교해보자.
save()
내부에서 발생한 런타임 예외를 밖으로 던짐 @Transactional
@Override
public Log save(Log logMessage) {
if (logMessage.getMessage().contains("exception")) {
throw new MyException("로그 저장 예외");
}
em.persist(logMessage);
log.info("Log : {}", logMessage.getMessage());
return logMessage;
}
save()
내부에서 발생한 런타임 예외를 처리 @Transactional
@Override
public Log save(Log logMessage) {
try {
if (logMessage.getMessage().contains("exception")) {
throw new MyException("로그 저장 예외");
}
em.persist(logMessage);
log.info("Log : {}", logMessage.getMessage());
return logMessage;
} catch (MyException me) {
log.info("exception");
}
}
1의 경우는 기존의 상황으로, rollback-only가 된다. 하지만 2의 경우는 논리 트랜잭션이 정상 커밋된다.
물리 트랜잭션이 커밋되는 상황
논리 트랜잭션이 롤백되는 경우