-
트랜잭션 커밋/롤백과 예외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