Tech/Spring

예외와 트랜잭션 커밋/롤백

봄의 개발자 2024. 1. 3.
728x90
반응형

예외가 발생했는데, 내부에서 예외를 처리 못 하고 트랜잭션 범위 밖으로 예외를 던지면 어떻게 될까?

 

  • 예외 발생 시 스프링 트랜잭션 AOP는 예외 종류에 따라 트랜잭션을 커밋하거나 롤백한다.
  • 언체크 예외인 RuntimeException, Error와 그 하위 예외가 발생하면 트랜잭션을 롤백한다.
  • 체크 예외인 Exception과 그 하위 예외가 발생하면 트랜잭션을 커밋한다.
  • 물론 정상 응답(리턴)하면 트랜잭션을 커밋한다.

 

테스트 코드

@SpringBootTest
public class RollbackTest {
    @Autowired
    RollbackService rollbackService;

    @Test
    void runtimeException() {
        assertThatThrownBy(() -> rollbackService.runtimeException()).isInstanceOf(RuntimeException.class);
    }

    @Test
    void checkedException() {
        assertThatThrownBy(() -> rollbackService.checkedException()).isInstanceOf(MyException.class);
    }

    @Test
    void rollbackFor() {
        assertThatThrownBy(() -> rollbackService.rollbackFor()).isInstanceOf(MyException.class);
    }

    @TestConfiguration
    static class RollbackTestConfig {
        @Bean
        RollbackService rollbackService() {
            return new RollbackService();
        }
    }

    @Slf4j
    static class RollbackService {
        // 런타임 예외 발생: 롤백
        @Transactional
        public void runtimeException() {
            log.info("call runtimeException");
            throw new RuntimeException();
        }

        // 체크 예외 발생: 커밋
        public void checkedException() throws MyException {
            log.info("call checkedException");
            throw new MyException();
        }

        // 체크 예외 rollbackFor 지정: 롤백
        @Transactional(rollbackFor = MyException.class)
        public void rollbackFor() throws MyException {
            log.info("call rollbackFor");
            throw new MyException();
        }
    }

    static class MyException extends Exception {

    }
}

 

 

runTimeException

2023-12-21T11:21:02.901+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [hello.springtx.exception.RollbackTest$RollbackService.runtimeException]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2023-12-21T11:21:02.902+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(972458732<open>)] for JPA transaction
2023-12-21T11:21:02.902+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@65ff4b8c]
2023-12-21T11:21:02.902+09:00 TRACE 20880 --- [           main] o.s.t.i.TransactionInterceptor           : Getting transaction for [hello.springtx.exception.RollbackTest$RollbackService.runtimeException]
2023-12-21T11:21:02.902+09:00  INFO 20880 --- [           main] h.s.e.RollbackTest$RollbackService       : call runtimeException
2023-12-21T11:21:02.902+09:00 TRACE 20880 --- [           main] o.s.t.i.TransactionInterceptor           : Completing transaction for [hello.springtx.exception.RollbackTest$RollbackService.runtimeException] after exception: java.lang.RuntimeException
2023-12-21T11:21:02.902+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : 2023-12-21T11:21:02.807+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [hello.springtx.exception.RollbackTest$RollbackService.rollbackFor]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-hello.springtx.exception.RollbackTest$MyException
2023-12-21T11:21:02.856+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1652372991<open>)] for JPA transaction
2023-12-21T11:21:02.863+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4df13dd0]
2023-12-21T11:21:02.863+09:00 TRACE 20880 --- [           main] o.s.t.i.TransactionInterceptor           : Getting transaction for [hello.springtx.exception.RollbackTest$RollbackService.rollbackFor]
2023-12-21T11:21:02.863+09:00  INFO 20880 --- [           main] h.s.e.RollbackTest$RollbackService       : call rollbackFor
2023-12-21T11:21:02.864+09:00 TRACE 20880 --- [           main] o.s.t.i.TransactionInterceptor           : Completing transaction for [hello.springtx.exception.RollbackTest$RollbackService.rollbackFor] after exception: hello.springtx.exception.RollbackTest$MyException
2023-12-21T11:21:02.864+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : **Initiating transaction rollback**
2023-12-21T11:21:02.864+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Rolling back JPA transaction on EntityManager [SessionImpl(1652372991<open>)]
2023-12-21T11:21:02.866+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1652372991<open>)] after transaction
2023-12-21T11:21:02.902+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Rolling back JPA transaction on EntityManager [SessionImpl(972458732<open>)]
2023-12-21T11:21:02.902+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(972458732<open>)] after transaction

 

checkedException

// java17 사용해서 그런지 강의에서는 Initialing transaction commit 출력되는데 실제로는 안나옴!
2023-12-21T11:21:02.897+09:00  INFO 20880 --- [           main] h.s.e.RollbackTest$RollbackService       : call checkedException

 

rollbackFor

2023-12-21T11:21:02.807+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [hello.springtx.exception.RollbackTest$RollbackService.rollbackFor]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-hello.springtx.exception.RollbackTest$MyException
2023-12-21T11:21:02.856+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1652372991<open>)] for JPA transaction
2023-12-21T11:21:02.863+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4df13dd0]
2023-12-21T11:21:02.863+09:00 TRACE 20880 --- [           main] o.s.t.i.TransactionInterceptor           : Getting transaction for [hello.springtx.exception.RollbackTest$RollbackService.rollbackFor]
2023-12-21T11:21:02.863+09:00  INFO 20880 --- [           main] h.s.e.RollbackTest$RollbackService       : call rollbackFor
2023-12-21T11:21:02.864+09:00 TRACE 20880 --- [           main] o.s.t.i.TransactionInterceptor           : Completing transaction for [hello.springtx.exception.RollbackTest$RollbackService.rollbackFor] after exception: hello.springtx.exception.RollbackTest$MyException
2023-12-21T11:21:02.864+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : **Initiating transaction rollback**
2023-12-21T11:21:02.864+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : **Rolling back** JPA transaction on EntityManager [SessionImpl(1652372991<open>)]
2023-12-21T11:21:02.866+09:00 DEBUG 20880 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1652372991<open>)] after transaction

 

  • 기본 정책과 무관하게 특정 예외를 강제로 롤백하고 싶으면 rollbackFor 를 사용하면 된다. (해당 예외의 자식도 포함된다.)
  • rollbackFor = MyException.class 을 지정했기 때문에 MyException 이 발생하면 체크 예외이지만 트랜잭션이 롤백된다.

 

더보기
- 스프링은 기본적으로 체크 예외는 비즈니스 의미가 있을 때 사용하고 런타임(언체크) 예외는 복구 불가능한 예외로 가정하기 때문에 커밋, 롤백 차이가 발생한다.
- 결론적으로 비즈니스 상황에 따라 체크 예외의 경우에도 트랜잭션을 커밋하지 않고, 롤백하고 싶을 때 rollbackFor 옵션을 사용하면 된다.
728x90
반응형

댓글