트랜잭션 전파 활용5 - 전파 롤백 에서 질문 있습니다.

24.03.03 22:43 작성 24.03.03 23:04 수정 조회수 85

0

/**
     * MemberService    @Transactional:ON
     * MemberRepository @Transactional:ON
     * LogRepository    @Transactional:ON Exception
     */

    @Test
    @Transactional
    void outerTxAllOn_logging_fail() { // 포함된 트랜잭션중 하나라도 런타임 에러가 발생한다면? 전체롤백!, (런타임 에러 기준임.)   (체크드 예외는 커밋임)
        //given
        String username = "outerTxAllOn_logging_fail";
        String username2 = "로그예외";
        //when
//        memberService.joinV1(username);
        memberService.joinV2(username2); // 예외발생
        log.info("--- joinV2 호출 종료");
        
        System.out.println("겟겟 "+memberRepository.find(username2).get().getUsername());
        System.out.println("겟겟 "+logRepository.find(username2).get().getMessage());
//        assertTrue(memberRepository.find(username2).isPresent());
//        assertTrue(logRepository.find(username2).isEmpty());

        // 결과적으로 여기메소드안의 작업은 전부 롤백은 된다.
    }

질문 1. 위 소스에서 트랜잭션이 끝나기 전에 memberRepository.find(username2) 를 수행했다면, 영속성 컨텍스트에 있던 insert들이 물리 트랜잭션에 수행되면서, 값이 조회가 되는 것이 맞는지 궁금합니다.

로그로 돌려봤을땐 값이 조회가 되었습니다.

질문 2. 물리 트랜잭션이 롤백되는 것이 핵심이며, 트랜잭션이 진행 중 런타임 에러가 예외로 처리되었기에, 트랜잭션이 끝나기 전 까지는 rollback()이 물리적으로 일어나지 않은 상태일 것입니다.
저는 강의랑 조금 다르게 서비스에 @Transactional을 넣지 않고, 테스트 메소드에 @Transactional을 넣어 봤습니다.
만약 이 상황에서 select를 날린다면, 물리 트랜잭션이 끝나지 않은(rollback되지 않은 상태)이므로 그 사이에 입력된 값은 조회가 되어야 하는게 맞아 보입니다.
이를 위해 앤티티매니저에서 flush()가 일어나고, 영속성 컨택스트에 대기중인 Insert문이 물리트랜잭션으로 전달되어 값이 조회가 되는 것으로 생각되나,
런타임 예외가 일어나서 logRepository.save(logFactory) -> em.persist(logMessage) 은 수행되지 않아야 정상일 것입니다.

System.out.println("겟겟 "+logRepository.find(username2).get().getMessage());

즉 위의 겟겟 logRepository.find(username2).get에서 메시지가 조회가 안 되어야 될 것입니다.
그런데 log_factory에 insert문이 하이버네이트에서 생성되고, log_factory에 값이 insert된 것이 조회가 되는건지 궁금합니다. (결과적으로 롤백은 되겠지만, 트랜잭션이 끝나기 전, 왜 저 find가 정상인건지 궁금합니다.)

public void joinV2(String username) { // 로그 저장 실패하면, throw 되는건 피하기 위해
    Member member = new Member(username);
    LogFactory logFactory = new LogFactory(username); // logFactory 객체 만드는이유? logFactory객체에 (username 정보) 저장하려고.

    log.info("--- memberRepository 호출 시작");
    memberRepository.save(member);
    log.info("--- memberRepository 호출 종료");

    try { // 로그저장 실패한다해도 예외 안던지기 위해 직접처리
        log.info("===--- logRepository 호출 시작");
        logRepository.save(logFactory);
        log.info("===--- logRepository 호출 종료");
    } catch (RuntimeException e) {
        log.info("로그 저장 실패, logRepository={}",logFactory.getMessage());
        log.info("로그 저장 실패, ErrorMessage={}",e.getMessage());
    }
    log.info("--- joinV2 종료");
}

 

@Transactional
public void save(LogFactory logMessage) {
    log.info("log 저장");
    em.persist(logMessage);
    if (logMessage.getMessage().contains("로그예외")) {
        log.info("log 저장시 예외 발생");
        throw new RuntimeException("예외 발생");
    }
}

김영한님 강의 스프링 DB2편 - 데이터 접근기술에서

트랜잭션 전파 활용5 를 보면서 테스트를 하다가 도저히 이해가 안가 이렇게 글을 남깁니다.

설명이 너무 어려운게 아니었는지 죄송스럽습니다.

답변 1

답변을 작성해보세요.

0

인프런 AI 인턴님의 프로필

인프런 AI 인턴

2024.03.04

안녕하세요, 인프런 AI 인턴입니다.

dbdomino님, JPA와 트랜잭션에 대한 질문을 주셨네요.

질문 1에 대한 답변입니다. 예, 영속성 컨텍스트에서 진행된 변경사항들은 트랜잭션이 끝나기 전까지는 물리적인 데이터베이스에 반영되지 않으나, 조회의 경우에는 영속성 컨텍스트에 있는 데이터를 참조할 수 있습니다. 그래서 memberRepository.find(username2)를 호출하면 현재 영속성 컨텍스트 내에 존재하는, 아직 커밋되지 않은 (물리적으로는 반영되지 않았지만) 데이터를 조회할 수 있습니다.

로그 결과를 통해서도 이를 확인할 수 있겠네요.

질문 2에 대한 답변입니다. 트랜잭션이 모두 끝나기 전까지는 rollback()이 물리적으로 발생하지 않는 것이 맞습니다. 이 말은 런타임 예외가 발생하면 현재 진행 중인 모든 데이터 변경사항은 롤백될 예정이지만, 실제 롤백은 commit() 호출 시점이나 예외가 발생한 코드 이후 롤백이 처리될 때까지는 데이터베이스에 반영되지 않기에, 그 사이에 수행되는 조회 작업에서는 변경사항이 보일 수 있습니다.

logRepository.find(username2).get().getMessage()을 호출했을 때 메시지가 조회되는 것은 예외 발생 이전에 해당 데이터가 데이터베이스에 이미 삽입됐거나, 영속성 컨텍스트에 존재하는 상태이기 때문일 수 있습니다. 이 상황에서는 실제 결과를 확인하기 위해서는 트랜잭션이 완전히 종료된 후에 데이터베이스 상태를 봐야 합니다.

트랜잭션 롤백과 관련된 내용은 출처인 스프링 데이터 JPA 문서에도 명시되어 있는 것과 같이, 예외 처리와 트랜잭션 경계 설정의 중요성을 잘 보여주고 있습니다:

따라서 개발할 때는 트랜잭션이 원활하게 수행되고 롤백되는 것을 고려하여 적절한 경계와 예외 처리를 설정하는 것이 중요합니다.