@Transactional과 REQUIRED 트랜잭션 전파의 기본 값은 REQUIRED 이다.
따라서 다음 둘은 같다.
- @Transactional(propagation = Propagation.REQUIRED)
- @Transactional
REQUIRED 는 기존 트랜잭션이 없으면 새로운 트랜잭션을 만들고, 기존 트랜잭션이 있으면 참여한다.
트랜잭션 전파


위 그림과 같이 MemberService, MemberRepository, LogRepository 가 하나의 트랜잭션의 묶여있는 상황을 가정해보자
public class MemberService {
private final MemberRepository memberRepository;
private final LogRepository repository;
@Transactional
public void joinV1(String username) {
Member member = new Member(username);
Log lm = new Log(username);
log.info("== memberRepository 호출 시작 ==");
memberRepository.save(member);
log.info("== memberRepository 호출 종료 ==");
log.info("== logRepository 호출 시작 ==");
repository.save(lm);
}
}
public class MemberRepository {
private final EntityManager em;
@Transactional
public void save(Member member) {
log.info("멤버 저장");
em.persist(member);
}
public Optional<Member> find(String username) {
...
}
}
public class LogRepository {
private final EntityManager em;
@Transactional
public void save(Log logMessage) {
log.info("로그 저장");
em.persist(logMessage);
if (logMessage.getMessage().contains("로그예외")) {
log.info("log 저장시 예외 발생");
throw new RuntimeException("예외 발생");
}
}
public Optional<Log> find(String message) {
...
}
}
MemberService.JoinV1을 실행시키게 되면 MemberRepository.save, LogRepository.save 전부 MemberService.JoinV1에서 만들어진 트랜잭션에 참여하게 된다. 그렇기 때문에 중간에 하나라도 Exception이 발생하게 되면 전부 롤백이 된다.
트랜잭션 전파 - 커밋

모든 논리 트랜잭션이 커밋 되어야만 물리 트랜잭션에 커밋이 일어나면서 실제 DB에 반영되게 된다.
트랜잭션 전파 - 롤백

논리 트랜잭션에서 하나라도 예외가 발생하게되면 트랜잭션이 롤백 되어진다.
롤백되는 이유
Exception이 발생되는 경우 트랜잭션 내부에서 rollbackOnly=true 로 변경하여 최종 커밋 시점에 UnexpectedRollbackException이 발생하게 된다.
트랜잭션 전파 - 롤백 복구

만약 LogRepository에서 예외가 발생하여 MemberService에서 예외처리를 하였다면 정상흐름으로 바꿀수 있을까?
정답은 아니다.
왜냐하면 이미 예외가 발생한 시점에서 트랜잭션 내부에 rollbackOnly=true로 변경되었기 때문이다.
그렇다면 어떻게 해야할까?
트랜잭션 전파 - REQUIRES_NEW
Transaction 옵션중 REQUIRES_NEW 를 사용하자

@Transaction(propagation = Propagation.REQUIRES_NEW)
를 사용하게 되면 트랜잭션이 완전히 분리 되므로 예외가 발생하여도 LogRepository에서만 롤백이 발생하고 외부 트랜잭션에는 영향이 전혀 없게 된다.
정리
논리 트랜잭션은 하나라도 롤백되면 관련된 물리 트랜잭션은 롤백되어 버린다.
이 문제를 해결하려면 REQUIRES_NEW 를 사용해서 트랜잭션을 분리해야 한다.
참고로 예제를 단순화 하기 위해 MemberService 가 MemberRepository , LogRepository 만 호출하지만 실제로는 더 많은 리포지토리들을 호출하고 그 중에 LogRepository 만 트랜잭션을 분리한다고 생각해보면 이해하는데 도움이 될 것이다.
주의
REQUIRES_NEW 를 사용하면 하나의 HTTP 요청에 동시에 2개의 데이터베이스 커넥션을 사용하게 된다.
References 및 사진 출처
김영한의 스프링 DB 2편 - 데이터 접근 활용 기술
'Spring > Transaction' 카테고리의 다른 글
| Spring Transaction 트랜잭션 옵션 (0) | 2023.08.16 |
|---|---|
| Spring Transaction AOP 주의사항 - 초기화 시점 (0) | 2023.08.15 |
| Spring Transaction AOP 주의사항 - 프록시 내부호출 문제해결 (0) | 2023.08.15 |
| Spring Transaction AOP 주의사항 - 프록시 내부호출 문제 (0) | 2023.08.15 |
| Spring Transaction 우선순위 (0) | 2023.08.15 |