Spring/Transaction

트랜잭션 전파

hwanguu 2023. 9. 5. 17:10

@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편 - 데이터 접근 활용 기술