ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Transaction 전파 propagation 알아보기
    Spring & JPA 2023. 10. 29. 22:03

     

    @Transactional을 서비스 레이어에서 보통 사용하는것을 알고있었는데
    정말 무지성으로 레포지토리에도 사용해버렸다.. 아마 레포지토리 테스트 짤때 @Transactional 사용하면서 레포지토리 도메인에도 사용해버린 것 같다.

     

    그래서 내 코드리뷰를 해주시는 서브멘토님께서(멘토님들 항상 감사합니다.. ) 트랜잭션 전파 키워드를 던져주셔서 알아보기로 했다!

     

    트랜잭션 전파의 기본 원리 - 트랜잭션 전파를 활용할 수 있는 상황 - 트랜잭션 전파 옵션 으로 알아보겠다.


     

    트랜잭션 전파 기본 원리

     

    해당 게시글의 그림 출처는 김영한님의 스프링 DB 2편 강의입니다!

     

     

     

    트랜잭션이 걸린 메서드를 실행킨뒤,

    내부의 트랜잭션이 걸려있는 메서드를 호출하는 상황이다.

    과연 이렇게 중첩된 상황에서 트랜잭션은 어떻게 작동을 하는지 자세히 살펴보자.

     

     PROPAGATION_REQUIRED,ISOLATION_DEFAULT
      Acquired Connection [HikariProxyConnection@1943867171 wrapping conn0] for JDBC
      transaction
      Switching JDBC Connection [HikariProxyConnection@1943867171 wrapping conn0] to
      manual commit

    처음의 외부 트랜잭션을 얻으면 다음과 같은 로그가 찍힌다. 

    Participating in existing transaction

    내부에서 또 트랜잭션을 얻으려고하면 이미 존재하는 트랜잭션을 사용한다는 로그가 찍힌다.
    Tip. 같은 쓰레드를 쓰면 트랜잭션 동기화매니저에서 항상 같은 커넥션을 가져다 쓴다. (Thread Local)

     

    이걸 그림으로 풀어보면 아래와 같다.

     

    외부 트랜잭션은 우리가 아는 TransacitonManager를 통해 일반 트랜잭션을 얻는 과정이랑 같다.

     

    반면 내부 트랜잭션은 기존에 외부 트랜잭션이 실행되고 있기 때문에 새로운 트랜잭션을 얻어내지 못하고 기존 트랜잭션(외부 트랜잭션)을 사용하게 된다.

     

    여기서 중요한건 기존 트랜잭션을 이어서 사용할 수는 있지만, 트랜잭션에 대한 권한은 없다는 것이다.

     

    외부 트랜잭션을 이어서 사용할 순 있지만 권한은 없다는 말의 뜻은

     

    내부 트랜잭션에서 커밋을 해도 흐름의 차이가 없다. 커밋로그를 찍어봐도 찍히지 않는다.

    결국엔 외부 트랜잭션의 커밋에 의존한다는 것이다.

     

    그림을 보면, 내부에선 커밋을 찍어도 커밋을 호출하지 못하고 외부 트랜잭션에 따라 판가름 된다는 것을 알 수 있다.

     

    외부에서 롤백을 하면 내부도 롤백이 되고, 외부에서 커밋하면 내부또한 커밋이 되는게 맞지만

    한가지 예외가 있는데,  내부 트랜잭션이 권한이 없어도, 내부 롤백으로 영향을 줄 수 있다.

    내부에서 롤백을 원해서 트랜잭션에 롤백 표시를 하고난 뒤 외부로 돌아가면, 외부에선 커밋이 불가하다.

    그림으로 나타내면 아래와 같다.

    내부에서  기존 트랜잭션을 얻어 rollbackOnly=true 설정을 하면 나중에 외부에서 커밋 요청을 하더라도 rollbackOnly옵션때문에 커밋이 불가하다. 로그를 찍어보면 아래와 같이 나온다.

     

     Global transaction is marked as rollback-only but transactional code requested
      commit
      Initiating transaction rollback

    이미 해당 트랜잭션엔 rollback-only가 걸려있으니

    응~넌 커밋 못해~  롤백이야~하면서 UnexpectedRollbackException 예외가 터진다. 

     

    중요한점

    그래서 외부트랜잭션과 동일한 커넥션을 쓰는 내부 트랜잭션에 대한 예외를 try-catch로 처리해서 정상흐름으로 바꾸려고해도 불가능하다.

    그렇다면 트랜잭션의 옵션을 이용해서 정상 흐름을 바꾸는 법을 알아보자!

     


    트랜잭션 전파 옵션을 활용할 수 있는 상황 

    예시) 한 쇼핑몰에서.. 

    옷 골랐고~

    결제해야지~~

    응??잔액 부족이네??

    이런 상황에 전부 롤백 처리해야할까?

    롤백처리하면 결제하기까지의 과정을 전부 다시해야되는데...?

    만약 첫주문이라면, 주소지도 새로 기입하고 쿠폰도 입력했는데 아오ㅋㅋ

     

    하나의 주문이 일어나는 과정에서 

    주문내역을 저장하는 작업과 결제하는 작업정도로 나눌 수 있다. 

    이것을 코드로 나타내면 다음과 같다.

     

    @RequiredArgsConstructor
    public class OrderService {
    	private final OrderRepository orderRepository;
    	private final paymentService;
        
        @Transactional
        public void order(Order order) {
        	log.info("order 호출");
            orderRepository.save(order); //주문내역을 저장한다.
            paymentService.pay(order); //주문내역을 결제한다. 
    	}
    }
    
    
    @Service
    public class PayemntService{
    	@Transactional
    	public void pay()(
        	// 비즈니스로직
        );
    }

     

     

    order()에서  pay()를 부르고 있는 상황이다. 

    만약 order()에서 save()는 성공하고 pay()가 롤백하면 전부 롤백될수도 있는데,
    나는 어지간하면 pay()가 실패해도 기존 save()로 저장한건 롤백하기 싫은 상황이다.


    트랜잭션 전파 옵션 활용하기

    상품주문 -> 주문내역 저장 ->결제 흐름에서 

    결제 부분을 아예 다른 트랜잭션을 사용하는 것이다!

    @Transactional(propagation = Propagation.REQUIRES_NEW)

     

     

    방법1

     

    아예 다른 트랜잭션이기에 하위 메서드에서 롤백이 터진것을 try-catch로 잡아서 정상흐름으로 처리하면 되는 상황이다.

     

    이때 또 고려해야하는점은

    결국엔 새로운 트랜잭션을 얻는 즉, 새로운 커넥션을 얻고 기존 커넥션은 반환되지 못한 상태이기 때문에 2개의 커넥션을 사용하고 있다.

    따라서 DB 성능을 고려해서 사용할 수 있어야한다.

     

    이 방법이 트랜잭션의 부분 롤백을 처리할수 있는 방법 1번이다.

    방법2

    이건 트랜잭션 옵션은 아니다.

    퍼사드 패턴을 이용한 방식인데 , 상위 인터페이스를 하나 더 만들어서

    구조적으로 각 트랜잭션이 겹쳐지지 않게 만드는것이다.

     

    세번째 방법은 https://yongnyeo.tistory.com/61

    내 이전 글에서 설명했던 방식인데,  UncheckedException 대신 Exception 을 활용해서 커밋을 한 후에, 내용을 변경하는 방식이다.

     

    절대적으로 좋은 것은 없으며 상황에 맞게 잘 사용할 수 있도록 트랜잭션을 많이 써봐야 할 것 같다.

     


    Reference

     

     

    https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard

Designed by Tistory.