ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 DB 데이터접근 기술 - 스프링 트랜잭션 전파 기본
    Spring-Boot/스프링 DB 2편 - 데이터 접근 기술 2022. 9. 27. 18:31

    * 트랜잭션 2개가 실행 시 주의! *

    - 트랜잭션1과 트랜잭션2가 같은 `conn0` 커넥션을 사용중이다. 이것은 중간에 커넥션 풀 때문에 그런 것이다.

    - 트랜잭션1은 `conn0` 커넥션을 모두 사용하고 커넥션 풀에 반납까지 완료

    - 이후에 트랜잭션2가 `conn0`를 커넥션 풀에서 획득함

    - 따라서 둘은 완전히 다른 커넥션으로 인지하는 것이 맞다

     

    - 히카리 커넥션 풀에서 커넥션을 다루는 프록시 객체의 주소가 트랜잭션1과 트랜잭션2는 서로 다르다.

    - 결과적으로 `conn0`을 통해 커넥션이 재사용 된 것이고 각각의 커넥션 풀에서 커넥션을 조회한 것을 확인

     

    - 트랜잭션이 각각 수행되면서 사용되는 DB 커넥션도 각각 다르다

    - 이 경우 트랜잭션을 각자 관리하기 때문에 전체 트랜잭션을 묶을 수 없다.

      예를 들어서 트랜잭션1이 커밋하고, 트랜잭션2가 롤백하는 경우 트랜잭션1에서 저장한 데이터는 커밋되고,

      트랜잭션2에서 저장한 데이터는 롤백된다.

     

    트랜잭션 전파

    - 트랜잭션이 이미 진행 중인데 추가로 트랜잭션을 수행 시 어떻게 동작할 지 결정하는 것이 트랜잭션 전파(propagation)이라 한다.

     

    * 외부 트랜잭션이 수행중인데, 내부 트랜잭션이 추가로 수행됨 *

    - 스프링은 이 경우 외부 트랜잭션과 내부 트랜잭션을 묶어서 하나의 트랜잭션을 만들어준다.

    - 내부 트랜잭션이 외부 트랜잭션에 참여하는 것이다. 이것이 기본 동작이고, 옵션을 통해 다른 동작방식도 선택할 수 있다.

     

    * 물리 트랜잭션, 논리 트랜잭션 *

    - 스프링은 이해를 돕기 위해 논리 트랜잭션과 물리 트랜잭션이라는 개념을 나눈다

    - 논리 트랜잭션들을 하나의 물리 트랜잭션으로 묶인다

    - 물리 트랜잭션은 우리가 이해하는 실제 데이터베이스에 적용되는 트랜잭션을 뜻한다. 실제 커넥션을 통해서 트랜잭션을 시작(`setAutoCommit(false)`) 하고, 실제 커넥션을 통해서 커밋, 롤백하는 단위이다.

    - 논리 트랜잭션은 트랜잭션 매니저를 통해 트랜잭션을 사용하는 단위이다.

    - 이러한 논리 트랜잭션 개념은 트랜잭션이 진행되는 중에 내부로 추가로 트랜잭션을 사용하는 경우 나타난다.

     

    * 원칙 *

    1. * 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다 *

    2. * 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다 *

     

     

     

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

    - 외부 트랜잭션 수행중인데, 내부 트랜잭션을 추가로 수행했다.

    - 외부 트랜잭션은 처음 수행된 트랜잭션이다. 이 경우 신규 트랜잭션( `isNewTransaction=true`) 이된다.

    - 내부 트랜잭션을 시작하는 시점에는 이미 외부 트랜잭션이 진행중인 상태이다. 이 경우 내부 트랜잭션은 외부 트랜잭션에 참여한다.

    - 트랜잭션 참여

        ㆍ내부 트랜잭션이 외부 트랜잭션에 참여한다는 뜻은 내부 트랜잭션이 외부 트랜잭션을

            그대로 이어 받아서 따른다는 뜻

        ㆍ다른 관점으로 보면 외부 트랜잭션의 범위가 내부 트랜잭션까지 넓어진다는 뜻

        ㆍ외부에서 시작된 물리적인 트랜잭션의 범위가 내부 트랜잭션까지 넓어진다는 뜻

        ㆍ정리하면 * 외부 트랜잭션과 내부 트랜잭션이 하나의 물리 트랜잭션으로 묶이는 것 *

    - 내부 트랜잭션은 이미 진행중인 외부 트랜잭션에 참여한다. 이 경우 신규 트랜잭션이 아니다( `isNEwTransaction=false` )

     

    - 정리하면 외부 트랜잭션만 물리 트랜잭션을 시작하고, 커밋한다.

    - 만약 내부 트랜잭션이 실제 물리 트랜잭션을 커밋하면 트랜잭션이 끝나버리기 때문에, 트랜잭션을 처음 시작한 외부 트랜잭션까지 이어갈 수 없다. 따라서 내부 트랜잭션은 DB 커넥션을 통한 물리 트랜잭션을 커밋하면 안된다.

    - 스프링은 이렇게 여러 트랜잭션이 함께 사용되는 경우, *처음 트랜잭션을 시작한 외부 트랜잭션이 실제 물리 트랜잭션을 관리* 하도록 한다. 이를 통해 트랜잭션 중복 커밋 문제를 해결한다.

     

    * 핵심 정리 *

    - 여기서 핵심은 트랜잭션 매니저에 커밋을 호출한다고해서 항상 실제 커넥션에 물리 커밋이 발생하지 않는다는 점이다

    - 신규 트랜잭션인 경우에만 실제 커넥션을 사용해서 물리 커밋과 롤백을 수행한다. 신규 트랜잭션이 아니면 실제 물리 커넥션을 사용하지 않는다.

    - 이렇게 트랜잭션이 내부에서 추가로 사용되면 트랜잭션 매니저에 커밋하는 것이 항상 물리 커밋으로 이어지지 않는다. 그래서 이 경우 논리 트랜잭션과 물리 트랜잭션을 나누게 된다. 또는 외부 트랜잭션과 내부 트랜잭션으로 나누어 설명하기도 한다.

    - 트랜잭션이 내부에서 추가로 사용되면, 트랜잭션 매니저를 통해 논리 트랜잭션을 관리하고, 모든 논리 트랜재션이 커밋되면  물리 트랜잭션이 커밋된다고 이해하면 된다.

     

    * 내부 롤백 정리 *

    - 논리 트랜잭션이 하나라도 롤백되면 물리 트랜잭션은 롤백된다

    - 내부 논리 트랜잭션이 롤백되면 롤백 전용 마크를 표시한다

    - 외부 트랜잭션을 커밋할 때 롤백 전용 마크를 확인한다. 롤백 전용 마크가 표시되어 있으면 물리 트랜잭션을 롤백하고,

      `UnexpectedRollbackException` 예외를 던진다.

     

    * 참고 *

    - 애플리케이션 개발에서 중요한 원칙은 모호함을 제거하는 것이다. 개발은 명확해야 한다

    - 커밋을 호출했는데 내부에서 롤백이 발생한 경우 모호하게 두면 아주 심각한 문제가 발생한다.

    - 기대한 결과가 다른 경우 예외를 발생시켜서 명확하게 문제를 알려주는 것이 좋은 설계이다.

     

    * REQUIRES_NEW *

    - 물리 트랜잭션을 분리하려면 내부 트랜잭션을 시작할 때 `REQUIRES_NEW` 옵션을 사용하면 된다

    - 외부 트랜잭션과 내부 트랜잭션이 각각 별도의 물리 트랜재션을 가진다.

    - 별도의 물리 트랜잭션을 가진다는 뜻은 DB 커넥션을 따로 사용한다는 뜻

    - 이 경우 내부 트랜잭션이 롤백되면서 로직2가 롤백되어도 로직1에서 저장한 데이터에는 영향을 주지 않는다.

    - 최종적으로 로직2가 롤백되고, 로직1은 커밋된다.

    * 정리 *

    - `REQUIRES_NEW` 옵션을 사용하면 물리 트랜잭션이 명확하게 분리된다.

    - `REQUIRES_NEW` 를 사용하면 데이터베이스 커넥션이 동시에 2개가 사용된다는 점을 주의

     

    다양한 전파 옵션

    - 스프링은 다양한 트랜잭션 전파 옵션을 제공한다. 

    - 전파 옵션에 별도의 설정을 하지 않으면 `REQUIRED`가 기본으로 사용된다.

    - 참고로 실무에서는 대부분 `REQUIRED` 옵션을 사용한다. 그리고 아주 가끔 `REQUIRED_NEW`을 사용하고, 나머지는 거의 사용하지 않는다.

     

    * REQUIRED *

    - 가장 많이 사용하는 기본 설정이다. 기존 트랜잭션이 없으면 생성하고, 있으면 참여한다

    - 트랜잭션이 필수라는 의미로 이해하면 된다. ( 필수이기 때문에 없으면 만들고, 있으면 참여한다. )

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

        ㆍ기존 트랜잭션 있음 : 기존 트랜잭션에 참여한다.

     

    * REQUIRES_NEW *

    - 항상 새로운 트랜잭션을 생성한다

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

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

     

    * SUPPORT *

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

        ㆍ기존 트랜잭션 없음 : 트랜잭션 없이 진행한다.

        ㆍ기존 트랜잭션 있음 : 기존 트랜잭션에 참여한다.

     

    * NOT_SUPPORT *

    - 트랜잭션을 지원하지 않는다는 의미이다.

        ㆍ기존 트랜잭션 없음 : 트랜재션 없이 진행한다.

        ㆍ기존 트랜잭션 있음 : 트랜잭션 없이 진행한다. ( 기존 트랜잭션은 보류한다 )

     

    * MANDATORY *

    - 의무사항이다. 트랜잭션이 반드시 있어야 한다. 기존 트랜잭션이 없으면 예외가 발생한다

        ㆍ기존 트랜잭션 없음 : `IllegalTransaciotnStateException` 예외 발생

        ㆍ기존 트랜잭션 있음 : 기존 트랜잭션에 참여한다

     

    * NEVER *

    - 트랜잭션을 사용하지 않는다는 의미이다. 기존 트랜잭션이 있으면 예외가 발생한다.

    - 기존 트랜잭션도 허용하지 않는 강한 부정의 의미로 이해하면 된다

        ㆍ기존 트랜잭션 없음 : 트랜잭션 없이 진행한다

        ㆍ기존 트랜잭션 있음 : `IllegalTransaciotnStateException` 예외 발생

     

    * NESTED *

        ㆍ기존 트랜잭션 없음 : 새로운 트랜잭션을 생성한다

        ㆍ기존 트랜재션 있음 : 중첩 트랜재션을 만든다.

    - 중첩 트랜잭션은 외부 트랜잭션의 영향을 받지만, 중첩 트랜재션은 외부에 영향을 주지 않는다

    - 중첩 트랜잭션은 롤백 되어도 외부 트랜잭션은 커밋할 수 있다

    - 외부 트랜잭션이 롤백 되면 중첩 트랜잭션도 함께 롤백된다

    - 참고

        ㆍJDBC savepoint 기능을 사용한다. DB드라이버에서 해당 기능을 지원하는지 확인이 필요하다

        ㆍ중첩 트랜잭션은 JPA에서는 사용할 수 없다.

     

    * 트랜잭션 전파와 옵션 *

    - `isolation`, `timeout`, `readOnly`는 트랜잭션이 처음 시작될 때만 적용된다. 트랜잭션에 참여하는 경우 적용되지 않는다.

    - 예를 들어서 `REQUIRED`를 통한 트랜잭션 시작, `REQUIRED_NEW`를 통한 트랜잭션 시작 시점에만 적용

     

    * @Transactional과 REQUIRED *

    - 트랜잭션 전파의 기본 값은 `REQUIRED`이다.

        ㆍ@Transactional(propagation = Propagation.REQUIRED)

        ㆍ@Transactional

    - `REQUIRED` 는 기존의 트랜잭션이 없으면 새로운 트랜잭션을 만들고, 기존의 트랜잭션이 있으면 참여

     

    * 정리 *

     - 논리 트랜잭션 중 하나라도 롤백되면 전체 트랜잭션은 롤백된다.

    - 내부 트랜잭션이 롤백되었는데, 외부 트랜잭션이 커밋되면 `UnexpectedRollbackException` 예외가 발생한다

    - `rollbackOnly` 상황에서 커밋이 발생하면 `UnexpectedRollbackException` 예외가 발생한다.

     

    * `REQUIRES_NEW` 주의 *

    - `REQUIRES_NEW` 를 사용하면 하나의 HTTP 요청에 동시에 2개의 데이터베이스 커넥션을 사용하게 된다. 따라서 성능이 중요한 곳에서는 이런 부분을 주의해서 사용해야 한다

    - `REQUIRES_NEW` 를 사용하지 안호 문제를 해결할 수 있는 단순한 방법이 있다면, 그 방법을 선택하는 것이 더 좋다.

    댓글

Designed by Tistory.