2025-05-29
스프링 트랜잭션
스프링 트랜잭션
Spring에서 트랜잭션을 올바르게 이해하고 활용하는 방법을 다룹니다. 프록시 기반 동작 원리, 전파 속성, 격리 수준, 예외 처리 시나리오를 살펴봅니다.
1. @Transactional의 동작 원리 (Proxy 기반)
Spring은 @Transactional을 만나면 실제 빈 대신 프록시 객체를 생성하여 트랜잭션을 관리합니다.
호출 흐름:
- Client → Proxy Bean 호출
- Proxy가
TransactionManager.getTransaction()으로 트랜잭션 시작 - Target(비즈니스 로직) 실행
- 예외 발생 여부에 따라 Commit 또는 Rollback
- 트랜잭션 종료
@Service
public class OrderService {
@Transactional
public void placeOrder(OrderDto dto) {
// 1) 재고 확인
// 2) 결제 처리
// 3) 주문 저장 (INSERT)
}
}2. 트랜잭션 전파 (Propagation)
메서드 간 트랜잭션 경계를 어떻게 공유할지 결정합니다.
| 전파 속성 | 설명 | |---|---| | REQUIRED | 기존 트랜잭션에 참여하거나 새로 생성 (기본값) | | REQUIRES_NEW | 항상 새 트랜잭션 생성; 기존 트랜잭션 일시 중단 | | SUPPORTS | 기존 트랜잭션이 있으면 참여; 없으면 트랜잭션 없이 실행 | | NOT_SUPPORTED | 기존 트랜잭션이 있으면 일시 중단; 트랜잭션 없이 실행 | | MANDATORY | 기존 트랜잭션 필수 | | NEVER | 기존 트랜잭션이 있으면 거부 | | NESTED | 기존 트랜잭션 내에 중첩 트랜잭션 생성 (Savepoint) |
@Transactional
public void outer() {
inner(); // inner()가 REQUIRED이면 outer의 트랜잭션에 참여
}
@Transactional(propagation = REQUIRES_NEW)
public void inner() {
// 독립 트랜잭션; inner의 롤백이 outer에 영향 없음
}3. 격리 수준 (Isolation Level)
동시성 문제를 어떻게 관리할지 제어합니다.
| 수준 | 설명 | 문제 | |---|---|---| | READ_UNCOMMITTED | 커밋되지 않은 데이터 읽기 허용 | Dirty Read | | READ_COMMITTED | 커밋된 데이터만 읽기 (DB 기본값) | Non-Repeatable Read | | REPEATABLE_READ | 동일 쿼리가 동일한 결과 반환 | Phantom Read | | SERIALIZABLE | 완전한 격리 (직렬화) | — |
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void updateInventory(Long productId, int qty) {
// 같은 트랜잭션 내 쿼리는 일관된 재고 수량을 반환
}핵심 개념:
- Dirty Read: 다른 트랜잭션의 커밋되지 않은 데이터 읽기
- Non-Repeatable Read: 같은 트랜잭션 내에서 재조회 시 값이 다름
- Phantom Read: 같은 조건으로 재조회 시 새로운 행이 나타남
4. 롤백되지 않는 예외
기본적으로 @Transactional은 런타임 예외/에러에서 롤백하지만, Checked 예외에서는 커밋합니다.
@Transactional
public void process() {
throw new IOException("Checked exception"); // 기본적으로 롤백되지 않음
}롤백 조건 변경:
@Transactional(rollbackFor = Exception.class)
public void processAll() throws IOException {
// IOException도 롤백 트리거
}롤백 제외:
@Transactional(noRollbackFor = CustomException.class)
public void noRollback() {
throw new CustomException();
// 예외가 발생해도 롤백되지 않음
}마무리
@Transactional은 프록시 기반으로 동작하며, 같은 클래스 내부 호출에서는 우회됨- 동시성 요구사항에 맞게 전파 속성과 격리 수준을 적절히 설정
- 기본 RuntimeException 동작을 넘어서
rollbackFor/noRollbackFor로 조정