ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 트랜잭션 전파
    Spring&SpringBoot 2025. 11. 20. 12:54

    1. commit()

    @Slf4j
    @SpringBootTest
    public class BasicTxTest {
        @Autowired
        PlatformTransactionManager txManager;
    
        @TestConfiguration
        static class config {
            @Bean
            public PlatformTransactionManager txManager(DataSource dataSource) {
                return new DataSourceTransactionManager(dataSource);
            }
        }
    
        @Test
        void commit() {
            log.info("트랜잭션 시작");
            TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
    
            log.info("트랜잭션 커밋 시작");
            txManager.commit(status);
            log.info("트랜잭션 커밋 완료");
        }
    }

     

     

    2. rollback()

    @Test
    void rollback() {
        log.info("트랜잭션 시작");
        TransactionStatus status = txManager.getTransaction(new DefaultTransactionDefinition());
    
        log.info("트랜잭션 롤백 시작");
        txManager.rollback(status);
        log.info("트랜잭션 롤백 완료");
    }

     

     

    3. double_commit()

    @Test
    void double_commit() {
        log.info("트랜잭션1 시작");
        TransactionStatus tx1 = txManager.getTransaction(new DefaultTransactionDefinition());
        log.info("트랜잭션1 커밋 ");
        txManager.commit(tx1);
    
        log.info("트랜잭션2 시작");
        TransactionStatus tx2 = txManager.getTransaction(new DefaultTransactionDefinition());
        log.info("트랜잭션2 커밋 ");
        txManager.commit(tx2);
    }

    • 트랜잭션1을 시작하고, 커넥션 풀에서 conn0 커넥션을 획득했다
    • 트랜잭션1을 커밋하고, 커넥션 풀에 conn0 커넥션을 반납했다
    • 트랜잭션2를 시작하고, 커넥션 풀에서 conn0 커넥션을 획득했다
    • 트랜잭션2를 커밋하고, 커넥션 풀에 conn0 커넥션을 반납했다
    • 히카리 커넥션이 풀이 반환해주는 커넥션을 다루는 프록시 객체의 주소가 서로 다르다 
    • 결과적으로 conn0을 통해 커넥션이 재사용 된 것을 확인할 수 있고 각각의 주소를 통해 커넥션 풀에서 커넥션을 조회했다

     

     

    4. double_commit_rollback()

    @Test
    void double_commit_rollback() {
        log.info("트랜잭션1 시작");
        TransactionStatus tx1 = txManager.getTransaction(new DefaultTransactionAttribute());
        log.info("트랜잭션1 커밋");
        txManager.commit(tx1);
            
        log.info("트랜잭션2 시작");
        TransactionStatus tx2 = txManager.getTransaction(new DefaultTransactionAttribute());
        log.info("트랜잭션2 롤백");
         txManager.rollback(tx2);
    }
    • 트랜잭션1은 커밋하고, 트랜잭션2는 롤백한다

     

     

    • 논리 트랜잭션들은 하나의 물리 트랜잭션으로 묶인다
    • 물리 트랜잭션은 우리가 이해하는 실제 데이터베이스에 적용되는 트랜잭션을 뜻한다
    • 논리 트랜잭션 개념은 트랜잭션이 진행되는 중에 내부에 추가로 트랜잭션을 사용하는 경우에 나타난다
    • 모든 논리 트랜잭션이 커밋되어야 물리 트랜잭션이 커밋된다
    • 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션은 롤백된다

     

     

    5. inner_commit()

    @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);
    }

    • txManager.getTransaction() 호출해서 외부 트랜잭션을 시작한다
    • 트랜잭션 매니저는 datasource를 통해 커넥션을 생성한다
    • 생성한 커넥션을 수동 커밋 모드로 설정한다 - 물리 트랜잭션 시작
    • 트랜잭션 매니저는 트랜잭션 동기화 매니저에 커넥션을 보관한다
    • 트랜잭션 매니저는 트랜잭션을 생성한 결과를 TransactionStatus에 담아서 반환하는데 신규 트랜잭션 여부가 담겨있다
    • 로직1이 사용되고 커넥션이 필요한 경우 트랜잭션 동기화 매니저를 통해 트랜잭션이 적용된 커넥션을 획득해서 사용한다
    • txManager.getTransaction()  호출해서 내부 트랜잭션이 시작한다 
    • 트랜잭션 매니저는 트랜잭션 동기화 매니저를 통해 트랜잭션이 존재하는지 확인한다
    • 기존 트랜잭션이 존재하므로 기존 트랜잭션에 참여한다 
    • 이미 물리 트랜잭션이 진행중이므로 로직이 기존에 시작된 트랜잭션을 자연스럽게 사용한다
    • 이후 로직은 트랜잭션 동기화 매니저에 보관된 기존 커넥션을 사용한다
    • 트랜잭션 매니저는 트랜잭션 생성 결과를 TransactionStatus에 담아 반환한다
    • 로직2가 사용되고 커넥션이 필요한 경우 트랜잭션 동기화 매니저를 통해 외부 트랜잭션이 보관한 커넥션을 획득해 사용한다

    • 로직2가 끝나고 트랜잭션 매니저를 통해 내부 트랜잭션을 커밋한다
    • 신규 트랜잭션이 아니기 때문에 실제 커밋을 호출하지 않는다
    • 로직1이 끝나고 트랜잭션 매니저를 통해 외부 트랜잭션을 커밋한다
    • 외부 트랜잭션은 신규 트랜잭션이기 때문에 DB 커넥션에 실제 커밋을 호출한다
    • 실제 데이터베이스에 커밋이 반영되고 물리 트랜잭션도 끝난다 

     

     

    5. outer_rollback()

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

    • 외부 트랜잭션이 물리 트랜잭션을 시작하고 롤백한다
    • 내부 트랜잭션은 직접 물리 트랜잭션에 관여하지 않는다 

     

     

    6. inner_rollback()

    @Test
    void inner_rollback() {
        log.info("외부 트랜잭션 시작");
        TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
    
        log.info("내부 트랜잭션 시작");
        TransactionStatus inner = txManager.getTransaction(new DefaultTransactionAttribute());
        log.info("내부 트랜잭션 롤백");
        txManager.rollback(inner);
    
        log.info("외부 트랜잭션 커밋");
        txManager.commit(outer);
    
        assertThatThrownBy(() -> txManager.commit(outer))
            .isInstanceOf(UnexpectedRollbackException.class);
    }

    • 외부 트랜잭션을 커밋할 때 UnexpectedRollbackException이 발생한다 

     

     

    7. inner_rollback_requires_new()

    • 물리 트랜잭션을 분리하려면 내부 트랜잭션을 시작할 때 REQUIRES_NEW 옵션을 사용한다 
    • 외부 트랜잭션과 내부 트랜잭션이 각각 별도의 물리 트랜잭션을 가진다 
    • 별도의 물리 트랜잭션을 갖는다는 말은 DB 커넥션 또한 각각 사용한다는 뜻이다 
    • 최종적으로 로직2는 롤백되고 로직1은 커밋된다 

     

     

    @Test
    void inner_rollback_requires_new() {
        log.info("외부 트랜잭션 시작");
        TransactionStatus outer = txManager.getTransaction(new DefaultTransactionAttribute());
        log.info("outer.isNewTransaction()={}", outer.isNewTransaction());
            
        log.info("내부 트랜잭션 시작");
        DefaultTransactionAttribute definition = new DefaultTransactionAttribute();
        definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
            
        TransactionStatus inner = txManager.getTransaction(definition);
        log.info("inner.isNewTransaction()={}", inner.isNewTransaction());
            
        log.info("내부 트랜잭션 롤백");
        txManager.rollback(inner); //롤백
            
        log.info("외부 트랜잭션 커밋");
        txManager.commit(outer); //커밋
    }

    • 내부 트랜잭션을 시작할 때 전파 옵션인 propagationBehavior에 PROPAGATION_REQUIRES_NEW 옵션을 줬다
    • 내부 트랜잭션을 시작할 때 기존 트랜잭션에 참여하는 것이 아니라 새로운 물리 트랜잭 션을 만들어서 시작하게 된다

     

     

    8. 전파 옵션

    • 전파 옵션에 별도의 설정을 하지 않으면 REQUIRED가 기본으로 사용된다
    • 실무에서는 대부분 REQUIRED 옵션을 사용하며 가끔 REQUIRED_NEW를 사용한다

    - REQUIRED

    • 기존 트랜잭션 없음 : 새로운 트랜잭션을 생성한다
    • 기존 트랜잭션 있음 : 기존 트랜잭션에 참여한다  

    - REQUIRED_NEW

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

    'Spring&SpringBoot' 카테고리의 다른 글

    데이터 접근 기술 - 스프링 데이터 JPA  (0) 2025.11.20
    트랜잭션 커밋/롤백과 예외  (0) 2025.11.20
    @Transaction  (0) 2025.11.13
    트랜잭션, DB 락  (0) 2025.11.12
    JDBC  (0) 2025.11.12
Designed by Tistory.