@Transactional

Spring에서 메서드 또는 클래스에 선언하여 해당 범위의 작업을 하나의 트랜잭션으로 묶어주는 어노테이션입니다.

기본 동작 원리

메서드 시작 → 트랜잭션 시작 → 비즈니스 로직 실행 → 성공 시 커밋 / 예외 시 롤백

기본 사용 예시

@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Autowired
    private PaymentRepository paymentRepository;

    @Transactional
    public void createOrder(OrderRequest request) {
        Order order = orderRepository.save(new Order(request));   // 1. 주문 저장
        paymentRepository.save(new Payment(order));               // 2. 결제 저장
        // 2에서 예외 발생 시 1도 함께 롤백
    }
}

주요 속성

속성설명기본값
propagation트랜잭션 전파 방식REQUIRED
isolation트랜잭션 격리 수준DB 기본값
readOnly읽기 전용 여부 (조회 성능 최적화)false
rollbackFor롤백할 예외 지정RuntimeException
timeout트랜잭션 제한 시간(초)-1 (무제한)
@Transactional(readOnly = true)
public List<Order> getOrders() {
    return orderRepository.findAll();  // 조회 전용, 성능 최적화
}

@Transactional(rollbackFor = Exception.class)
public void process() throws Exception {
    // 체크 예외도 롤백 대상에 포함
}

전파 옵션 (Propagation)

옵션설명
REQUIRED기존 트랜잭션이 있으면 참여, 없으면 새로 생성 (기본값)
REQUIRES_NEW항상 새 트랜잭션 생성, 기존 트랜잭션은 일시 중단
NESTED기존 트랜잭션 내에 중첩 트랜잭션(세이브포인트) 생성
MANDATORY기존 트랜잭션이 반드시 있어야 함, 없으면 예외
SUPPORTS트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행
NOT_SUPPORTED트랜잭션 없이 실행, 기존 트랜잭션은 일시 중단

동작 원리 (프록시 기반)

클라이언트 → Spring AOP 프록시 → 트랜잭션 시작 → 실제 메서드 → 커밋/롤백

Spring은 프록시 객체를 통해 트랜잭션을 관리하기 때문에, 주의할 점이 있습니다.

격리 수준 (Isolation)

동시에 여러 트랜잭션이 실행될 때 서로 얼마나 영향을 주는지 결정합니다.

격리 수준Dirty ReadNon-Repeatable ReadPhantom Read
READ_UNCOMMITTED발생발생발생
READ_COMMITTEDO 방지발생발생
REPEATABLE_READO 방지O 방지발생
SERIALIZABLEO 방지O 방지O 방지

MySQL InnoDB 기본값: REPEATABLE_READ / 대부분 DB 기본값: READ_COMMITTED

@Transactional(isolation = Isolation.READ_COMMITTED)
public void processOrder() { ... }

REQUIRES_NEW 실전 예시

@Service
public class OrderService {

    @Transactional
    public void placeOrder(Order order) {
        orderRepository.save(order);
        notificationService.sendEmail(order);  // 실패해도 주문은 살리고 싶을 때
    }
}

@Service
public class NotificationService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendEmail(Order order) {
        // 별도 트랜잭션 → 여기서 예외 나도 placeOrder 롤백 안 됨
    }
}

자주 발생하는 실수

1. 내부 호출 시 트랜잭션 미적용

@Service
public class OrderService {

    public void outer() {
        this.inner();  // 프록시를 거치지 않아 @Transactional 미적용!
    }

    @Transactional
    public void inner() {
        // 트랜잭션이 걸리지 않음
    }
}

같은 클래스 내부에서 this로 호출하면 프록시를 거치지 않으므로 트랜잭션이 동작하지 않습니다. 별도 클래스로 분리하여 해결합니다.

2. private 메서드에 적용

@Transactional
private void process() {  // 프록시가 오버라이드할 수 없어 미적용!
    // ...
}

@Transactional은 public 메서드에만 적용 가능합니다.

3. 체크 예외 롤백 누락

@Transactional
public void process() throws IOException {
    // IOException 발생 시 롤백되지 않음!
    // rollbackFor = IOException.class 추가 필요
}

트랜잭션 AOP 동작 흐름

@Transactional은 트랜잭션 AOP를 통해 적용됩니다. 클라이언트가 Bean을 호출하면 실제 객체가 아니라 프록시가 먼저 요청을 받고, 프록시는 TransactionManager로 트랜잭션을 시작한 뒤 실제 메서드를 실행합니다. 성공하면 커밋하고, 예외가 발생하면 롤백합니다.

→ 스프링 트랜잭션 AOP 동작 흐름에 대해서 설명해주세요 → 어떤 예외가 발생하면 트랜잭션을 롤백하나요?

CSLOG에서 보기