Spring Boot에서 효율적인 트랜잭션 관리 방법

Spring Boot 애플리케이션에서 트랜잭션 관리는 데이터 일관성을 유지하는 핵심 요소입니다. 올바르게 설정하지 않으면 데이터 손실이나 불일치 문제가 발생할 수 있습니다. 이번 글에서는 Spring의 트랜잭션 관리 전략효율적인 사용 방법을 정리해보겠습니다.


1.  트랜잭션 관리 방식

Spring Boot에서 트랜잭션을 관리하는 방법은 크게 두 가지로 나뉩니다.

 

1) 선언적 트랜잭션 관리 (@Transactional)

가장 일반적인 방식으로, 메서드나 클래스에 @Transactional 어노테이션을 사용하여 트랜잭션을 관리합니다.

  • 해당 메서드 실행 중 예외가 발생하면 자동으로 롤백
  • 커밋 또는 롤백을 신경 쓰지 않아도 되므로 유지보수가 용이

 

2) 프로그래밍 방식 트랜잭션 관리 (TransactionTemplate, PlatformTransactionManager)

트랜잭션을 직접 제어해야 할 때 사용합니다.

@Service
public class PaymentService {
    private final TransactionTemplate transactionTemplate;
    private final PaymentRepository paymentRepository;
    public PaymentService(PlatformTransactionManager transactionManager, PaymentRepository paymentRepository) {
    	this.transactionTemplate = new TransactionTemplate(transactionManager);
        this.paymentRepository = paymentRepository;
    }
    public void processPayment(Payment payment) {
    	transactionTemplate.executeWithoutResult(status -> {
        	paymentRepository.save(payment); // 추가적인 비즈니스 로직 수행
    	});
    }
}
  • 세밀한 트랜잭션 제어가 필요할 때 유용
  • 코드가 길어지고 복잡해질 수 있음

2. @Transactional 사용 시 주의할 점

1) 프록시 기반이므로 같은 클래스 내에서 호출하면 동작하지 않음

Spring의 @Transactional은 기본적으로 프록시 방식을 사용하기 때문에, 같은 클래스 내에서 트랜잭션 메서드를 호출하면 적용되지 않습니다.

잘못된 예:

@Service
public class UserService {
    @Transactional
    public void createUser(User user) {
    	saveUser(user);
    }
    
    public void saveUser(User user) {
    	// 같은 클래스 내 호출 → 트랜잭션이 적용되지 않음
        userRepository.save(user);
    }
}

 

해결 방법:

  • 외부에서 호출하도록 설계하거나, self-invocation 문제를 해결하기 위해 AOP를 활용합니다.
  • @Transactional을 사용하는 메서드를 다른 빈(Bean)에서 호출하도록 설계합니다.

2) readOnly 옵션을 적절히 활용하기

데이터 조회 시 @Transactional(readOnly = true)를 사용하면 성능 최적화에 도움이 됩니다.

@Transactional(readOnly = true)
public User getUser(Long id) {
    return userRepository.findById(id).orElseThrow();
}
  • Hibernate는 readOnly = true일 때 더티 체킹(DIRTY CHECKING)을 수행하지 않으므로 성능이 향상됩니다.
  • 단, 읽기 전용 트랜잭션 내에서 save()를 호출하면 예외(Exception)가 발생할 수 있으므로 주의해야 합니다.

3. 트랜잭션 전파 옵션 (Propagation)

Spring은 트랜잭션이 중첩될 때 동작을 제어할 수 있도록 여러 가지 전파(Propagation) 옵션을 제공합니다.

  • 전파 옵션 종류
REQUIRED 기본값. 기존 트랜잭션이 있으면 참여, 없으면 새 트랜잭션 생성
REQUIRES_NEW 기존 트랜잭션을 무시하고 항상 새로운 트랜잭션 시작
NESTED 기존 트랜잭션 내에서 중첩 트랜잭션 실행
SUPPORTS 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행
NOT_SUPPORTED 트랜잭션 없이 실행
NEVER 트랜잭션이 있으면 예외 발생
MANDATORY 기존 트랜잭션이 없으면 예외 발생

 

@Service
public class NotificationService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendNotification(Notification notification) {
    	notificationRepository.save(notification);
    }
}
 
  • REQUIRES_NEW를 사용하면 부모 트랜잭션과 별개로 커밋/롤백이 가능합니다.
  • 전파 옵션을 잘 활용하면 한쪽 로직이 실패해도 다른 트랜잭션이 영향을 받지 않도록 할 수 있습니다.

4. 트랜잭션 롤백 전략

1) 기본 롤백 동작

  • @Transactional은 체크 예외(Checked Exception) 발생 시 롤백하지 않고, 런타임 예외(Runtime Exception) 발생 시 롤백합니다.
@Transactional
public void processOrder() throws IOException {
    // 체크 예외 throw new IOException("파일 처리 오류");
    // 롤백되지 않음
}
@Transactional public void processPayment() {
    throw new RuntimeException("결제 오류");
    // 롤백됨
}
 

2) 특정 예외에 대해 롤백 설정

  • rollbackFor 옵션을 사용하여 특정 예외가 발생해도 롤백되도록 설정할 수 있습니다.
@Transactional(rollbackFor = IOException.class)
public void processFile() throws IOException {
    throw new IOException("파일 처리 오류"); // 롤백됨
}
  • 반대로, 특정 예외에서 롤백되지 않도록 noRollbackFor 옵션을 사용할 수도 있습니다.
@Transactional(noRollbackFor = IllegalArgumentException.class)
public void updateUser(User user) {
    throw new IllegalArgumentException("잘못된 입력"); // 롤백되지 않음
}

 

Spring Boot에서 트랜잭션을 적절히 관리하면 데이터 무결성을 보장하고 성능을 최적화할 수 있습니다.

@Transactional을 적극 활용하되, 내부 호출(self-invocation) 문제를 주의
읽기 전용 트랜잭션(readOnly = true)을 적절히 활용하여 성능을 최적화
Propagation 옵션을 이해하고, 비즈니스 로직에 맞게 선택
체크 예외와 런타임 예외를 rollbackFor, noRollbackFor를 통해 롤백 여부 제어