회원 가입을 시도한 로그를 남기는데 실패하더라도 회원 가입은 유지되어야 한다.
이 요구사항을 만족하기 위해서 로그와 관련된 물리 트랜잭션을 별도로 분리해보자. 바로 REQUIRES_NEW를 사용하는 것이다.
recoverException_success
@Test
void recoverException_success() {
//given
String username = "로그예외_recoverException_success";
//when
memberService.joinV2(username);
//then: member 저장, log 롤백
assertTrue(memberRepository.find(username).isPresent());
assertTrue(logRepository.find(username).isEmpty());
}
LogRepository - save()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void save(Log logMessage)
이렇게 해서 기존 트랜잭션에 참여하는 REQUIRED 대신에, 항상 신규 트랜잭션을 생성하는 REQUIRES_NEW 를 적용하자.
예외를 복구하는 memberService.joinV2()를 사용한다는 점도 주의하자.



LogRepository 에서 예외가 발생한다. 예외를 던지면 LogRepository 의 트랜잭션 AOP가 해당 예외를 받는다.
REQUIRES_NEW 를 사용한 신규 트랜잭션이므로 물리 트랜잭션을 롤백한다. 물리 트랜잭션을 롤백했으므로 rollbackOnly 를 표시하지 않는다. 여기서 REQUIRES_NEW 를 사용한 물리 트랜잭션은 롤백되고 완전히 끝이 나버린다.
이후 트랜잭션 AOP는 전달 받은 예외를 밖으로 던진다.
예외가 MemberService 에 던져지고, MemberService 는 해당 예외를 복구한다. 그리고 정상적으로 리턴한다.
정상 흐름이 되었으므로 MemberService 의 트랜잭션 AOP는 커밋을 호출한다.
커밋을 호출할 때 신규 트랜잭션이므로 실제 물리 트랜잭션을 커밋해야 한다. 이때 rollbackOnly 를 체크한다.
rollbackOnly 가 없으므로 물리 트랜잭션을 커밋한다.
이후 정상 흐름이 반환된다.
정리
주의
예를 들면 다음과 같이 REQUIRES_NEW 를 사용하지 않고 구조를 변경하는 것이다.

이렇게 하면 HTTP 요청에 동시에 2개의 커넥션을 사용하지는 않는다. 순차적으로 사용하고 반환하게 된다.
물론 구조상 REQUIRES_NEW 를 사용하는 것이 더 깔끔한 경우도 있으므로 각각의 장단점을 이해하고 적절하게 선택해서 사용하면 된다.