ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 트랜잭션 커밋/롤백과 예외
    Spring&SpringBoot 2025. 11. 20. 09:33

    예외 발생

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

     

     

    application.properties 수정

    # application.properties
    logging.level.org.springframework.transaction.interceptor=TRACE
    logging.level.org.springframework.jdbc.datasource.DataSourceTransactionManager=DEBUG
    
    # JPA log
    logging.level.org.springframework.orm.jpa.JpaTransactionManager=DEBUG
    logging.level.org.hibernate.resource.transaction=DEBUG
    • 트랜잭션이 커밋되었는지 롤백되었는지 로그로 확인할 수 있다

     

     

    1. 런타임 예외 발생 : 롤백

    @SpringBootTest
    public class RollbackTest {
        @Autowired RollbackService rollbackService;
    
        @Test
        void runtimeException() {
            rollbackService.runtimeException();
        }
    
        @TestConfiguration
        static class TestContext{
            @Bean
            RollbackService rollbackService(){
                return new RollbackService();
            }
        }
    
        @Slf4j
        static class RollbackService {
            // 런타임 예외 발생 : 롤백
            @Transactional
            public void runtimeException() {
                log.info("call runtimeException");
                throw new RuntimeException();
            }
    }

     

     

     

    2. 체크 예외 발생, rollbackFor 지정 : 롤백

    @SpringBootTest
    public class RollbackTest {
        @Autowired RollbackService rollbackService;
    
        @Test
        void checkedException() {
            Assertions.assertThatThrownBy(() -> rollbackService.checkedException())
                    .isInstanceOf(MyException.class);
        }
    
        @TestConfiguration
        static class TestContext{
            @Bean
            RollbackService rollbackService(){
                return new RollbackService();
            }
        }
    
        @Slf4j
        static class RollbackService {
            // 체크 예외 발생 : 커밋
            @Transactional
            public void checkedException() throws MyException{
                log.info("call checkedException");
                throw new MyException();
            }
    
        static class MyException extends Exception {
    
        }
    }

     

     

     

    3. 체크 예외 발생, rollbackFor 지정 : 롤백

    package hello.springtx.exception;
    
    import lombok.extern.slf4j.Slf4j;
    import org.assertj.core.api.Assertions;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    import org.springframework.boot.test.context.TestConfiguration;
    import org.springframework.context.annotation.Bean;
    import org.springframework.transaction.annotation.Transactional;
    
    @SpringBootTest
    public class RollbackTest {
        @Autowired RollbackService rollbackService;
    
        @Test
        void rollbackFor() {
            Assertions.assertThatThrownBy(() -> rollbackService.rollbackFor())
                    .isInstanceOf(MyException.class);
        }
    
        @TestConfiguration
        static class TestContext{
            @Bean
            RollbackService rollbackService(){
                return new RollbackService();
            }
        }
    
        @Slf4j
        static class RollbackService {
            // 체크 예외 발생, rollbackFor 지정 : 롤백
            @Transactional(rollbackFor = MyException.class)
            public void rollbackFor() throws MyException{
                log.info("call checkedException");
                throw new MyException();
            }
        }
    
        static class MyException extends Exception {
    
        }
    }

    • @Transactional(rollbackFor = Exception.class) : 기본 정책에 추가로 어떤 예외가 발생할 대 롤백할지 지정할 수 있다

     

     

    4. 예시

    • 스프링은 기본적으로 체크 예외는 비즈니스 의미가 있을 때 사용하고 언체크 예외는 복구 불가능한 예외로 가정한다

     

    [요구사항]

    • 정상 - 주문시 결제를 성공하면 주문  데이터를 저장하고 결제 상태를 완료 처리한다
    • 시스템예외 - 주문 시 내부에 복구 불가능한 예외가 발생하면 전체 데이터를 롤백한다
    • 비즈니스예외 - 주문 시 결제 잔고가 부족하면 주문 데이터를 저장하고 결제 상태를 대기로 처리한다
      • NotEnoughMoneyException 체크 예외가 발생하는데 시스템의 문제가 아니라 고객의 잔고가 부족한 비즈니스 상황이 예외인 것이다 
      • 비즈니스 예외는 매우 중요하고 반드시 처리해야 하는 경우가 많기 때문에 체크 예외를 고려할 수 있다

     

     

    NotEnoughMoneyException

    public class NotEnoughMoneyException extends Exception{
        public NotEnoughMoneyException(String message){
            super(message);
        }
    }
    • 결제가 부족하면 발생하는 비즈니스 예외
    • Exception을 상속받아 체크예외로 생성

     

     

    Order

    @Entity
    @Table(name="orders")
    @Getter
    @Setter
    public class Order {
        @Id
        @GeneratedValue
        private Long id;
    
        private String userName; // 정상,예외,잔고부족
        private String payStatus; // 대기, 완료
    }

     

     

    OrderRepository

    public interface OrderRepository extends JpaRepository<Order, Long> {
    }

     

     

    OrderService

    @Slf4j
    @Service
    @RequiredArgsConstructor
    public class OrderService {
        private final OrderRepository orderRepository;
    
        @Transactional
        public void order(Order order) throws NotEnoughMoneyException {
            log.info("order 호출");
            orderRepository.save(order);
    
            log.info("결제 프로세스 시작");
            if (order.getUserName().equals("예외")) {
                log.info("시스템 예외 발생");
                throw new RuntimeException("시스템 예외 발생");
            } else if (order.getUserName().equals("잔고부족")) {
                log.info("잔고 부족 비즈니스 예외 발생");
                order.setPayStatus("대기");
                throw new NotEnoughMoneyException("잔고가 부족합니다.");
            } else {
                // 정상 승인
                log.info("정상 승인");
                order.setPayStatus("완료");
            }
            log.info("결제 프로세스 완료");
        }
    }

     

     

    OrderServiceTest - complete()

    @Slf4j
    @SpringBootTest
    class OrderServiceTest {
        @Autowired OrderService orderService;
        @Autowired OrderRepository orderRepository;
    
        @Test
        void complete() throws NotEnoughMoneyException {
            // given
            Order order = new Order();
            order.setUserName("정상");
    
            // when
            orderService.order(order);
    
            // then
            Order findOrder = orderRepository.findById(order.getId()).get();
            Assertions.assertThat(findOrder.getPayStatus()).isEqualTo("완료");
        }
    }
    • 프로세스 정상 수행

     

     

    OrderServiceTest -  runtimeException()

    @Slf4j
    @SpringBootTest
    class OrderServiceTest {
        @Autowired OrderService orderService;
        @Autowired OrderRepository orderRepository;
    
        @Test
        void runtimeException() throws NotEnoughMoneyException {
            // given
            Order order = new Order();
            order.setUserName("예외");
    
            // when
            Assertions.assertThatThrownBy(() -> orderService.order(order))
                            .isInstanceOf(RuntimeException.class);
    
            // then
            Optional<Order> orderOptional = orderRepository.findById(order.getId());
            assertThat(orderOptional.isEmpty()).isTrue();
        }
    }

    • RuntimeException("시스템 예외") 발생
    • 런타임 예외로 롤백이 수행되었기 때문에 Order에는 데이터가 저장되지 않는다

     

     

    @Slf4j
    @SpringBootTest
    class OrderServiceTest {
        @Autowired OrderService orderService;
        @Autowired OrderRepository orderRepository;
    
        @Test
        void bizException() {
            // given
            Order order = new Order();
            order.setUserName("잔고부족");
    
            // when
            try {
                orderService.order(order);
            } catch (NotEnoughMoneyException e) {
                log.info("고객에게 잔고 부족을 알리고 별도의 계좌로 입금하도록 안내");
            }
    
            // then
            Order findOrder = orderRepository.findById(order.getId()).get();
            assertThat(findOrder.getPayStatus()).isEqualTo("대기");
        }
    }

    • NotEnoughMoneyException("잔고가 부족합니다") 발생
    • 체크 예외로 커밋이 수행되었기 때문에 Order 데이터가 저장된다
    728x90

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

    스프링 트랜잭션 전파  (0) 2025.11.20
    데이터 접근 기술 - 스프링 데이터 JPA  (0) 2025.11.20
    @Transaction  (0) 2025.11.13
    트랜잭션, DB 락  (0) 2025.11.12
    JDBC  (0) 2025.11.12
Designed by Tistory.