1. 트랜잭션 전파란?
- 트랜잭션 전파(Transaction Propagation)는 이미 진행 중인 트랜잭션이 있을 때 추가 트랜잭션 처리를 어떻게 할지 정의하는 방식입니다.
정보
트랜잭션 전파는 주로 서비스 계층의 메서드 호출 시 트랜잭션 간의 관계를 설정하는 데 사용됩니다.
1.1 전파가 필요한 이유
- 여러 트랜잭션이 중첩되어 실행될 때 어떻게 처리할지 결정해야 함
- 각각의 비즈니스 로직에 맞는 트랜잭션 범위 설정 필요
- 성능과 데이터 일관성 사이의 균형 유지
2. 트랜잭션 전파 옵션
스프링에서 제공하는 7가지 전파 옵션에 대해 알아보겠습니다.
2.1 REQUIRED (기본값)
@Transactional(propagation = Propagation.REQUIRED)
public void someMethod() {
// 메서드 내용
}
- 가장 많이 사용되는 기본 전파 옵션
- 특징:
- 진행 중인 트랜잭션이 있으면 참여
- 없으면 새 트랜잭션 생성
- 사용 시나리오:
- 대부분의 서비스 메서드
- 단일 트랜잭션으로 처리되어야 하는 비즈니스 로직
경고
REQUIRED를 사용할 때는 예외 처리에 주의해야 합니다. 하나의 트랜잭션에서 발생한 예외가 전체 트랜잭션을 롤백시킬 수 있습니다.
2.2 REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void independentOperation() {
// 항상 새로운 트랜잭션에서 실행되는 로직
}
- 항상 새로운 트랜잭션을 생성
- 특징:
- 기존 트랜잭션은 일시 중단
- 새 트랜잭션이 완전히 독립적으로 실행
- 각각의 트랜잭션이 별도로 커밋/롤백
- 사용 시나리오:
- 로깅
- 감사(Audit) 기록
- 알림 발 송
2.3 SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS)
public void optionalTransaction() {
// 트랜잭션이 있으면 참여, 없어도 실행
}
- 트랜잭션이 필수가 아닌 경우 사용
- 특징:
- 진행 중인 트랜잭션이 있으면 참여
- 없어도 트랜잭션 없이 실행
- 사용 시나리오:
- 조회 전용 메서드
- 트랜잭션이 없어도 되는 단순 작업
2.4 NOT_SUPPORTED
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void nonTransactionalOperation() {
// 트랜잭션 없이 실행되어야 하는 로직
}
- 트랜잭션 없이 실행
- 특징:
- 진행 중인 트랜잭션이 있으면 일시 중단
- 트랜잭션 없이 실행
- 사용 시나리오:
- 단순 조회 작업
- 트랜잭션 오버헤드를 피하고 싶은 경우
2.5 MANDATORY
@Transactional(propagation = Propagation.MANDATORY)
public void mustHaveTransaction() {
// 반드시 트랜잭션 내에서 실행되어야 하는 로직
}
- 반드시 트랜잭션 내에서 실행되어야 함
- 특징:
- 진행 중인 트랜잭션이 없으면 예외 발생
- 트랜잭션이 있어야만 실행 가능
- 사용 시나리오:
- 중요한 비즈니스 로직
- 반드시 트랜잭션 컨텍스트가 필요한 경우
2.6 NEVER
@Transactional(propagation = Propagation.NEVER)
public void mustNotHaveTransaction() {
// 트랜잭션이 있으면 안 되는 로직
}
- 트랜잭션이 있으면 안 되는 경우
- 특징:
- 진행 중인 트랜잭션이 있으면 예외 발생
- 트랜잭션 없이 실행
- 사용 시나리오:
- 읽기 전용 작업
- 트랜잭션이 필요 없는 단순 작업
2.7 NESTED
@Transactional(propagation = Propagation.NESTED)
public void nestedOperation() {
// 중첩 트랜잭션에서 실행되는 로직
}
- 중첩 트랜잭션 생성
- 특징:
- 진행 중인 트랜잭션이 있으면 중첩 트랜잭션 생성
- 없으면 새 트랜잭션 생성
- 부모 트랜잭션의 커밋/롤백에 영향을 받음
- 사용 시나리오:
- 특정 작업을 독립적으로 롤백하고 싶은 경우
- 대용량 배치 작업
경고
NESTED는 모든 데이터베이스에서 지원하지 않을 수 있습니다. 주로 SAVEPOINT를 사용하여 구현됩니다.
3. 실제 사용 예시
3.1 주문 처리 시스템
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
@Transactional
public void processOrder(Order order) {
// 메인 트랜잭션 (REQUIRED)
paymentService.processPayment(order); // REQUIRED
inventoryService.updateStock(order); // REQUIRED
notificationService.sendEmail(order); // REQUIRES_NEW
}
}
@Service
public class NotificationService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendEmail(Order order) {
// 별도의 트랜잭션에서 이메일 발송
// 실패해도 주문/결제는 영향 없음
}
}
3.2 로깅 시스템
@Service
public class AuditService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logAction(String action, String user) {
AuditLog log = new AuditLog(action, user);
auditRepository.save(log);
}
}
@Service
public class UserService {
@Autowired
private AuditService auditService;
@Transactional
public void updateUser(User user) {
userRepository.save(user);
// 로깅은 별도 트랜잭션으로 처리
auditService.logAction("USER_UPDATE", user.getId());
}
}
4. 전파 속성 선택 시 고려사항
4.1 성능 고려사항
- 트랜잭션 생성과 관리에는 비용이 발생
- REQUIRES_NEW는 새로운 트랜잭션을 생성하므로 오버헤드 발생
- 불필요한 트랜잭션은 피하는 것이 좋음
4.2 데이터 일 관성
- REQUIRED는 하나의 트랜잭션으로 묶여 일관성 보장
- REQUIRES_NEW는 독립적인 트랜잭션으로 처리되어 부분 실패 가능
- 비즈니스 요구사항에 따라 적절한 선택 필요
4.3 예외 처리
@Service
public class ComplexService {
@Transactional
public void process() {
try {
// 주요 비즈니스 로직
mainOperation();
// 부가적인 작업 (별도 트랜잭션)
additionalOperation();
} catch (Exception e) {
// 예외 처리
handleException(e);
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
private void additionalOperation() {
// 실패해도 메인 트랜잭션에 영향 없는 작업
}
}
5. 주의사항
5.1 자기 호출의 한계
@Service
public class SelfInvocationService {
@Transactional
public void method1() {
// 트랜잭션 적용됨
method2(); // 트랜잭션 적용 안됨!
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method2() {
// 같은 클래스 내부 호출은 트랜잭션 적용 안됨
}
}
경고
같은 클래스 내의 메서드 호출은 프록시를 통해 동작하는 스프링 AOP의 특성상 트랜잭션 전파가 동작하지 않습니다.
5.2 해결 방법
@Service
public class GoodDesignService {
@Autowired
private GoodDesignService self; // 자기 자신을 주입
@Transactional
public void method1() {
// 트랜잭션 적용됨
self.method2(); // 프록시를 통한 호출로 트랜잭션 적용됨
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void method2() {
// 정상적으로 새로운 트랜잭션 생성
}
}
6. 결론
트랜잭션 전파는 복잡한 비즈니스 로직에서 데이터 일관성을 보장하면서도 유연한 트랜잭션 관리를 가능하게 합니다. 각 전파 옵션의 특성을 이해하고 상황에 맞게 적절히 사용하는 것이 중요합니다.
팁
대부분의 경우 기본값인 REQUIRED를 사용하되, 특별한 요구사항이 있는 경우에만 다른 옵션을 고려하세요.