Transaction 전파가 뭡니까?

yboy·2022년 8월 10일
3

Learning Log 

목록 보기
16/41
post-thumbnail

학습동기

이전 포스팅에서 Transactional을 학습한 이후에 Transactional 전파에 대한 학습을 약속했기 때문에 Transactional 전파라는 주제로 추가학습을 진행하게 됐다.

학습내용

트랜잭션 전파(Transaction propagation)

어떤 트랜잭션이 동작중인 과정에서 다른 트랜잭션을 실행할 경우 어떻게 처리하는 가에 대한 개념이다.

그럼 트랜잭션 전파는 왜 개발자가 알아야하고 어떤 상황에 제어해 줘야 할까??🧐🧐
예시를 들어보자. 예시는 스프링 기준이다.

게시판에 글을 작성하는 기능을 하는 로직을 생각해 보자. 글을 임시 저장하는 기능이 글 작성 중간 중간에 수행되고 동시에 로깅을 해야하는 상황이다. 이때 로깅 작업에서 예외가 발생했다. 이때 트랜잭션 전파 옵션이 Default이면 Rollback이 진행될 때, 임시 저장되는 기능도 함게 Rollback된다.

위의 예시 상황에서는 임시 저장 기능은 Rollback이 되선 안된다.(로깅 작업에서 문제가 발생했다고 사용자가 요구한 요청을 무시하는 건 아니라는 판단 하에) 그럼 기본 트랜잭션 옵션을 변경해줘야 한다. 로깅 작업과 임시저장 작업이 개별적인 트랜잭션 단위에서 움직이게 바꿔 줘야 하는데... 그 옵션이 뭘까???(미리 말하자면 Default인 REQUIRED에서 REQUIRES_NEW 옵션으로 바꿔 줘야 한다.)

트랜잭션 전파 속성 종류에 대해 알아보면서 답을 찾아보자.

REQUIRED

  • Default 속성
  • 기본적으로 부모 트랜잭션이 있으면 부모 트랜잭션에 종속
  • 부모 트랜잭션이 없을 경우에는 새로운 트랜잭션 생성

REQUIRES_NEW

  • 부모 트랜잭션을 무시하고 무조건 새로운 트랜잭션 생성
  • 부모 트랜잭션은 현재 트랜잭션이 종료될 때 까지 대기상태로 존재
  • 자기 트랜잭션에서 예외가 발생해 Rollback이 진행돼도 부모 트랜잭션에 Rollback이 전파되지 않는다.
  • 부모 트랜잭션에서 예외가 발생해 Rollback이 진행돼도 자기 트랜잭션에 Rollback이 전파되지 않는다.

SUPPORTS

  • 부모 트랜잭션이 있을 때만 해당 부모 트랜잭션에 종속
  • 부모 트랜잭션이 없으면 트랜잭션이 적용되지 않는다.

NOT_SUPPORTED

  • 부모 트랜잭션이 있으면 부모 트랜잭션을 대기시키고 트랜잭션 없이 실행
  • 부모 트랜잭션이 없으면 트랜잭션 없이 실행된다.

MANDATORY

  • 부모 트랜잭션이 있을 때만 해당 부모 트랜잭션에 종속
  • 부모 트랜잭션이 없으면 예외가 발생한다.

NEVER

  • 트랜잭션이 적용되면 안되는 경우에 사용
  • 부모 트랜잭션이 있으면 예외가 발생한다.
  • 부모 트랜잭션이 없으면 트랜잭션 없이 실행된다.

NESTED

  • 부모 트랜잭션이 있으면 새로운 트랜잭션 생성
  • REQUIRED_NEW와는 다르다.
  • 부모 트랜잭션의 커밋과 롤백에는 영향을 받지만 자신의 커밋과 롤백은 부모 트랜잭션에게 영향을 주지 않는다.
  • 자식 트랜잭션이 실패하면 부모 트랜잭션은 Rollback되지 않는다.
  • 부모 트랜잭션이 실패하면 자식 트랜잭션은 Rollback 된다.

그럼 이제 Spring에서 transaction전파를 이용해 보자.

예시)

    @Transactional
    public List<Crew> update(List<Crew> crews) {
    	System.out.println("Transaction1: " + TransactionSynchronizationManager.getCurrentTransactionName());
        crewRepository.deletAll();
        return saveAll(crews);
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public List<Crew> saveAll(List<Crew> crews) {
        System.out.println("Transaction2: " + TransactionSynchronizationManager.getCurrentTransactionName());
        return crewRepository.saveAll(crews);
    }

위 예시는 스프링에서 제공하는 @Transactional을 이용해 전파 옵션을 다르게 적용한 예시이다.(전파옵션을 설정해야 하는 마땅한 예시는 아닌 것 같으나 실험에 의의를 둔다.) update()의 처음 실행 부분과 REQUIRES_NEW 옵션을 적용해 다른 트랜잭션이 생성되도록 유도한 saveAll()의 현재 트랜잭션을 확인해 보기 위해 TransactionSynchronizationManager.getCurrentTransactionName()을 이용해 현재 트랜잭션을 확인해 보았다.

    @Test
    void update() {
        crewRepository.save(new Crew("페퍼"));
        crewRepository.save(new Crew("잉"));

        List<Crew> 요청된_5기_크루들 = List.of(
                new Crew("솔트"),
                new Crew("슈가"),
                new Crew("다시다"),
                new Crew("페퍼")

        );

        List<Crew> 등록된_5기_크루들 = crewService.update(요청된_5기_크루들);
    }

하지만 예상했던 결과와는 달리 REQUIRES_NEW 옵션을 통해 새로운 트랜잭션이 생성되지 않고 있었다🥲🧐. 트랜잭션 전파 속성을 "REQUIRES_NEW"로 지정했는데, 왜 부모 트랜잭션이랑 독립된 트랜잭션으로 실행되지 않을까??

이유가 무엇일까??
이유는 프록시에 있었다. @Transactianal은 프록시 기반으로 동작하기 때문에 외부에서 접근할 때 AOP를 통해서 프록시 객체를 접근할 수 있다. 그러나 클래스내에서 다른 메서드를 호출하게 되면 프록시로 접근하지 않고 직접 접근하기 때문에 메서드에 선언해 놓은 @Transactinal이 정상적으로 동작하지 않는다.

해결책
그럼 해결책은 간단하다. 다른 서비스를 만들고 거기에 새로운 트랜잭션으로 동작할 메서드를 작성해 사용하는 것이다.

    @Transactional
    public List<Crew> update(List<Crew> crews) {
    	System.out.println("Transaction1: " + TransactionSynchronizationManager.getCurrentTransactionName());
        crewRepository.deletAll();
        return crewSaveService.saveAll(crews);
    }
@RequiredArgsConstructor
@Transactional
@Service
public class CrewSaveService {

    private final CrewRepository crewRepository;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public List<Crew> saveAll(List<Crew> crews) {
        System.out.println("Transaction2: " + TransactionSynchronizationManager.getCurrentTransactionName());
        return crewRepository.saveAll(crews);
    }

}

이 말은 즉, '한 서비스의 메서드에서 두 개 이상의 트랜잭션을 생성하지 않도록 하라.' 라는 의미이다. 앞으로 서비스를 분리할 때, 고려해야 할 부분이라고 생각한다.
근데 해결 방법이 이것 밖에는 없을까? 아직 프록시와 AOP 개념이 모호한 것 같다. 이 개념들을 공부해 보고 다른 해결책을 찾아보도록 해야겠다.(모르는 게 산더미 같다...)

마무리

트랜잭션 전파에 대해 간략하게 알아 보았다. 어떤 상황에 각각의 전파 옵션이 적용될 지는 아직 생각은 잘 안나지만 분명 다양한 전파 옵션을 적용해 볼 날이 올 것이다. 그때 이번에 학습한 옵션들을 보고 상황에 맞게 잘 적용해보자! 또한 @Transactional 애노테이션 동작의 기본이 되는 개념인 AOP프록시에 대해 알게 되었다.(안 건 아니고 들어본 것...) 아직은 잘 모르겠다.... 차차 공부해 보도록 하자!(토비의 스프링에 AOP 내용만 150p라는데...) 홧팅하자!!

0개의 댓글