Spring의 Transaction 중첩

60jong·2023년 11월 20일
0

Spring

목록 보기
9/9
post-thumbnail

트랜잭션이 중첩되게 되는 경우, 기본값으로는 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가 되는 조건

그렇다면 트랜잭션 매니저가 트랜잭션 정보를 rollback-only로 수정하게 되는 조건이 무엇일까?

논리 트랜잭션 내부에서 처리되지 않은 런타임 예외가 있을 때이다.

두 상황을 비교해보자.

    1. 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;
        }
    1. 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의 경우는 논리 트랜잭션이 정상 커밋된다.

결론

  • 물리 트랜잭션이 커밋되는 상황

    • 논리 트랜잭션이 모두 커밋됐을 때
      !!! 예외를 catch한 관점이 아니라, 트랜잭션 매니저가 트랜잭션 정보를 다루는 입장에서 생각하자!!!
  • 논리 트랜잭션이 롤백되는 경우

    • 처리되지 못한 런타임 예외가 있을 때
profile
울릉도에 별장 짓고 싶다

0개의 댓글