스프링 트랜잭션 전파 - 트랜잭션 분리

바그다드·2023년 8월 3일
0

스프링 트랜잭션

목록 보기
4/5

이전의 포스팅에서 스프링 트랜잭션 전파에 대해 알아보았다.
스프링 트랜잭션에는 원칙이 있었는데,
논리 트랜잭션중 하나라도 롤백이되면 물리 트랜잭션은 롤백이 되어야 하고,
모든 논리 트랜잭션이 커밋이 되어야 물리 트랜잭션도 커밋이 된다고 하였다.

그렇다면 트랜잭션 별로 커밋과 롤백을 각각 따로 적용시키려면 어떻게 해야할까?


1. REQUIRES_NEW

트랜잭션 별로 커밋과 롤백을 분리해서 수행하려면 어떻게 해야할까?
물리 트랜잭션을 분리하면 된다. 그럼 각 트랜잭션 별로 커밋과 롤백을 별도로 수행할 수 있게 된다.
즉, 커넥션을 새로 획득하는 것이다.

코드로 확인해보자

    @Test
    void inner_rollback_requires_new() {
        log.info("외부 트랜잭션 시작");
        TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
        log.info("outer.isNewTransaction()={}", outer.isNewTransaction());

        log.info("내부 트랜잭션 시작");
        // requires_new 옵션을 부여하여
        // 새로운 물리 트랜잭션을 생성함
        DefaultTransactionAttribute definition = new DefaultTransactionAttribute();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        TransactionStatus inner = txManager.getTransaction(definition);
        log.info("inner.isNewTransaction()={}", inner.isNewTransaction()); // true

        log.info("내부 트랜잭션 롤백");
        txManager.rollback(inner);

        log.info("외부 트랜잭션 커밋");
        txManager.commit(outer);
    }
  • getTransaction()에 DefaultTransactionAttribute를 넘기기 전
    'setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW)'
    이런 설정을 통해 기존 트랜잭션을 사용하는 것이 아닌 새로운 물리 트랜잭션을 시작하도록 설정해준다.

결과를 확인해보면

  • 외부 트랜잭션은 conn0을
    내부 트랜잭션은 conn1을, 각각 다른 커넥션을 사용하고 있는 것을 확인할 수 있다.
  • 또한 외부 트랜잭션과 내부 트랜잭션 모두 isNewTransaction()=true라는 값을 반환하여 각각 물리 트랜잭션이 수행되는 것을 확인할 수 있다.

덕분에 내부 트랜잭션이 롤백되더라도 외부 트랜잭션은 커밋을 수행할 수 있게 되었다.

요청 흐름

  • 외부 트랜잭션
  1. 외부 트랜잭션을 시작한다,
  2. 트랜잭션 매니저는 데이터 소스를 이용해 트랜잭션을 생성한다.
  3. 커넥션을 setAutoCommit(false)을 통해 수동 커밋으로 설정하여 물리 트랜잭션을 시작한다.
  4. 커넥션을 트랜잭션 동기화 매니저에 보관한다.
  5. 트랜잭션 관련 정보를 TransactionStatus에 담아서 반환한다.
    • 신규 트랜잭션이므로 isNewTransaction()=True를 반환한다.
  6. 로직1에서는 트랜잭션 동기화 매니저에서 con1 획득해 사용한다.
  • 내부 트랜잭션
  1. REQUIRES_NEW을 지정해 트랜잭션 매니저로부터 새로운 트랜잭션을 생성하고, 시작한다.
  2. 트랜잭션 매니저는 데이터 소스로 새로운 커넥션을 생성한다.
  3. setAutoCommit(false)를 통해 수동 커밋으로 설정하여 물리 트랜잭션을 시작한다.
  4. 커넥션을 트랜잭션 동기화 매니저에 보관한다.
  5. 트랜잭션 관련 정보를 TransactionStatus에 담아 반환한다.
    • 트랜잭션을 새로 생성했으므로 isNewTransaction()=True를 반환한다.
  6. 로직2에서는 트랜잭션 동기화 매니저에서 con2를 획득해 사용한다.

응답 흐름

업로드중..

  • 내부 트랜잭션
  1. 롤백을 호출한다.
  2. 트랜잭션 매니저는 신규 트랜잭션인지 여부에 따라 다른 동작을 하는데, 신규 트랜잭션이므로 커넥션에 롤백을 호출한다.
  3. con2의 물리 트랜잭션을 롤백하고, 해당 트랜잭션 종료 및 con2를 반환한다.
  • 외부 트랜잭션
  1. 커밋을 호출한다.
  2. 트랜잭션 매니저는 신규 트랜잭션인지 여부에 따라 다른 동작을 하는데, 신규 트랜잭션이므로 커넥션에 커밋을 호출한다.
  3. 먼저 트랜잭션의 롤백 전용 표시를 확인한다. 표시가 없으므로 커밋한다.
  4. con1의 물리 트랜잭션을 커밋하고, 해당 트랜잭션 종료 및 con1을 반환한다.

REQUIRES_NEW라는 속성을 이용해 외부 트랜잭션과 내부 트랜잭션을 분리하여 처리할 수 있게되었다.
다만, 이 경우 커넥션이 동시에 2개가 사용된다는 점을 주의해야 한다.
- 50명이 사용할 경우 100개의 커넥션이 점유가 된다.


2. 다양한 전파 옵션

1. REQUIRED

가장 많이 사용하는 옵션, 필수이므로 없으면 생성, 있으면 참여한다.
기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.

2. REQUIRES_NEW

항상 새로운 트랜잭션을 생성한다.
기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
기존 트랜잭션 있음: 새로운 트랜잭션을 생성한다.

3. SUPPORT

트랜잭션을 지원한다는 뜻이다. 기존 트랜잭션이 없으면, 없는대로 진행하고, 있으면 참여한다.
기존 트랜잭션 없음: 트랜잭션 없이 진행한다.
기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.

4. NOT_SUPPORT

트랜잭션을 지원하지 않는다는 의미이다.
기존 트랜잭션 없음: 트랜잭션 없이 진행한다.
기존 트랜잭션 있음: 트랜잭션 없이 진행한다. (기존 트랜잭션은 보류한다

5. MANDATORY

의무사항이라는 뜻으로 없으면 예외 발생, 있으면 참여한다.
기존 트랜잭션 없음: IllegalTransactionStateException 예외 발생
기존 트랜잭션 있음: 기존 트랜잭션에 참여한다.

6. NEVER

MANDATORY와 반대이다. 없으면 트랜잭션 없이 진행, 있으면 예외 발생
기존 트랜잭션 없음: 트랜잭션 없이 진행한다.
기존 트랜잭션 있음: IllegalTransactionStateException 예외 발생

7.NESTED

중첩된 이라는 뜻으로 내부는 외부에 영향을 받지만 외부는 내부에 영향을 받지 않는다.
기존 트랜잭션 없음: 새로운 트랜잭션을 생성한다.
기존 트랜잭션 있음: 중첩 트랜잭션을 만든다.

  • 외부 트랜잭션의 영향은 받지만, 외부에 영향을 주지 않는다.
  • 중첩 트랜잭션이 롤백 되어도 외부 트랜잭션은 커밋할 수 있다.
    다만, 외부 트랜잭션이 롤백 되면 중첩 트랜잭션도 함께 롤백된다.
  • JDBC savepoint 기능을 사용하기 때문에 DB드라이버에서 기능을 지원하는지 확인이 필요하다.
    또한 JPA에서는 사용이 불가능하다.

3. 트랜잭션 전파와 다른 옵션

isolation , timeout , readOnly는 트랜잭션이 처음 시작될 때만 적욛되고, 기존 트랜잭션에 참여하는 경우에는 적용되지 않는다.
따라서 REQUIRED에서 외부 트랜잭션의 시작, REQUIRED_NEW를 통한 시작 시점에만 적용이 된다.

이것으로 스프링 트랜잭션 전파에 대해 깊이 있게 알아볼 수 있었다. 다음 포스팅에서는 이런 트랜잭션 전파를 어디에 적용할 수 있는지 알아보도록 하자.

출처 : 김영한 - 스프링 DB 2편

profile
꾸준히 하자!

0개의 댓글