본문 바로가기
IT

[DB 트랜잭션] @Transactional 속성(전파 방식 Propagation)

by 유나니나노 2025. 2. 27.
반응형

@Transactional?

Spring의 @Transactional은 트랜잭션을 관리하는 기능을 제공하는 어노테이션 즉, 데이터베이스 작업(INSERT, UPDATE, DELETE 등)이 안전하게 수행되도록 보장하는 역할

 

 

필요한 이유

  • 데이터베이스에서 여러 작업이 수행될 때, 일부만 성공하고 일부는 실패하면 데이터 정합성 문제 발생 가능
  • 이를 방지하기 위해 트랜잭션을 사용하면 4가지 원칙(ACID)을 보장할 수 있음
  • ACID 원칙
원칙 설명
Atomicity (원자성) 모든 작업이 성공하면 커밋(Commit), 하나라고 실패하면 롤백(Rollback)
Consistency (일관성) 트랜잭션 실행 후 데이터가 항상 일관성을 유지해야 함
Isolation (고립성) 트랜잭션끼리 서로 독립적으로 실행되어야 함
Durability (지속성) 트랜잭션이 완료되면 데이터가 영구적으로 저장됨

 

기본 동작

  • 메서드 실행 시 트랜잭션을 시작함
  • 메서드가 정상 종료되면 자동으로 커밋(commit)
  • 예외가 발생하면 자동으로 롤백(rollback)
@Service
public class OrderService {
    @Autowired
    private OrderRepository orderRepository;

    @Transactional // 트랜잭션 시작
    public void createOrder() {
        orderRepository.save(new Order("상품1", 1000)); // 데이터 저장 (아직 커밋되지 않음)
        if (true) {
            throw new RuntimeException("예외 발생!"); // 예외 발생 → 롤백됨
        }
    }
}

 


Propagation 속성

  • @Transactional을 사용할 때, propagation 속성을 통해 트랜잭션의 전파 방식을 설정할 수 있음
  • 트랜잭션 전파란 메서드가 실행될 때 이미 트랜잭션이 있는 경우, 기존 트랜잭션을 어떻게 처리할지 정의하는 것
  • Spring에서는 총 7가지 전파 옵션이 있음
전파 방식 동작 방식 부모 트랜잭션이 있을 때 부모 트랜잭션이 없을 때 자식 예외 발생 시  부모 예외 발생 시
REQUIRED(기본값) 부모 트랜잭션에 참여 기존 트랜잭션 사용 트랜잭션 없이 실행 부모와 함께 롤백 부모와 함께 롤백
REQUIRES_NEW 새로운 트랜잭션 생성 기존 트랜잭션 일시 정지, 새 트랜잭션 생성 새 트랜잭션 실행 자식만 롤백, 부모 영향 없음 부모 롤백되어도 자식은 커밋 유지
MANDATORY 반드시 부모 트랜잭션 필요 기존 트랜잭션 사용 예외 발생 부모와 함께 롤백 부모와 함께 롤백
SUPPORT 있으면 참여, 없으면 X 기존 트랜잭션 사용 트랜잭션 없이 실행 부모 트랜잭션이면 같이 롤백 부모 트랜잭션이면 함께 롤백
NOT_SUPPORTED 트랜잭션 없이 실행 기존 트랜잭션 일시 정지, 트랜잭션 없이 실행 트랜잭션 없이 실행 롤백 불가(이미 커밋됨) 부모 영향 없음
NEVER 트랜잭션 있으면 예외 발생 예외 발생 트랜잭션 없이 실행 롤백 불가(이미 커밋됨) 부모 영향 없음
NESTED 부모 트랜잭션 애부에서 서브 트랜잭션(Savepoint) 생성 부모 트랜잭션 내부에서 서브 트랜잭션 실행 새 트랜잭션 생성(REQUIRED)와 동일 자식만 롤백, 부모는 유지 부모 롤백 시 자식도 함께 롤백

 

1. REQUIRED (기본값)

  • 설명: 현재 트랜잭션이 존재하면 참여하고, 없으면 새 트랜잭션을 생성함
  • 특징: 가장 일반적인 옵션이며, 기본값이므로 @Transactional(propagation = Propagation.REQUIRED)를 명시하지 않아도 동일하게 동작
@Service
public class ParentService {
    @Autowired
    private ChildService childService;

    @Transactional
    public void parentMethod() {
        System.out.println("부모 트랜잭션 시작");
        childService.childMethod();
        System.out.println("부모 트랜잭션 끝");
    }
}

@Service
public class ChildService {
    @Transactional(propagation = Propagation.REQUIRED) // 기본값이므로 생략 가능
    public void childMethod() {
        System.out.println("자식 메서드 실행");
    }
}

1. parentMethod()가 실행되면서 트랜잭션 시작

2. childMethod()가 실행될 때 기존 트랜잭션에 참여(새 트랜잭션을 만들지 않음)

3. parentMethod()가 정상 종료되면 하나의 트랜잭션으로 커밋

  • 사용 예시
    • 대부분의 일반적인 서비스 로직
    • 부모-자식 관계의 서비스에서 같은 트랜잭션을 유지하고 싶을 때

 

2. REQUIRES_NEW

  • 설명: 기존 트랜잭션이 있으면 일시 정지하고, 새로운 트랜잭션을 생성함
  • 특징: 기존 트랜잭션과 독립적으로 커밋 또는 롤백할 수 있음
@Service
public class ParentService {
    @Autowired
    private ChildService childService;

    @Transactional
    public void parentMethod() {
        System.out.println("부모 트랜잭션 시작");
        childService.childMethod();
        System.out.println("부모 트랜잭션 끝");
    }
}

@Service
public class ChildService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void childMethod() {
        System.out.println("자식 트랜잭션 시작");
        throw new RuntimeException("자식 메서드에서 예외 발생");
    }
}

1. parentMethod()가 실행되면서 트랜잭션이 시작됨

2. childMethod()가 실행될 때 기존 트랜잭션을 일시 정지하고 새로운 트랜잭션을 시작함

3. chileMethod()에서 예외가 발생하여 롤백됨

4. 하지만 부모 트랜잭션은 별개의 트랜잭션이므로 정상적으로 커밋됨

  • 사용 예시
    • 부모 트랜잭션과 독립적으로 실행해야 하는 작업(ex. 로그 기록, 이메일 전송)
    • 특정 서비스가 실패해도 전체 트랜잭션에 영향을 주지 않도록 하고 싶을 때

3. MANDATORY

  • 설명: 반드시 기존 트랜잭션이 존재해야 하며, 없으면 예외 발생
  • 특징: 단독 실행이 불가능하고, 반드시 상위 트랜잭션이 있어야 함
@Service
public class ParentService {
    @Autowired
    private ChildService childService;

    public void parentMethod() {
        childService.childMethod(); // 트랜잭션 없이 실행하면 예외 발생
    }
}

@Service
public class ChildService {
    @Transactional(propagation = Propagation.MANDATORY)
    public void childMethod() {
        System.out.println("기존 트랜잭션이 없으면 예외 발생");
    }
}

1. parentMethod()에서 chiledMethod()를 호출하는데, parentMethoe()는 트랜잭션이 없음

2. childMethod()는 반드시 기존 트랜잭션이 필요하므로 예외 발생

  • 사용 예시
    • 반드시 트랜잭션이 유지되어야 하는 작업(ex. 금융 거래)
    • 트랜잭션이 보장되어야 하는 메서드에서 실수로 트랜잭션 없이 호출되는 것을 방지하고 싶을 때

 

4. SUPPORTS

  • 설명: 현재 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행
  • 특징: 트랜잭션이 있어도 되고, 없어도 되는 유연한 방식
@Service
public class ParentService {
    @Autowired
    private ChildService childService;

    public void parentMethod() {
        childService.childMethod(); // 트랜잭션 없이 실행됨
    }
}

@Service
public class ChildService {
    @Transactional(propagation = Propagation.SUPPORTS)
    public void childMethod() {
        System.out.println("트랜잭션이 없어도 실행 가능");
    }
}

1. parentMethod()에서 childMethod() 호출

2. 부모 트랜잭션이 없으므로 childMethod()도 트랜잭션 없이 실행됨

  • 사용 예시
    • 트랜잭션이 필수는 아니지만, 가능하면 트랜잭션을 참여하는 메서드
    • 조회(Read-Only) 작업에서 유용

 

5. NOT_SUPPORTED

  • 설명: 현재 트랜잭션이 있으면 일시 정지하고, 트랜잭션 없이 실행
  • 특징: 트랜잭션을 사용하지 않아야 하는 경우 유용함
@Service
public class ParentService {
    @Autowired
    private ChildService childService;

    @Transactional
    public void parentMethod() {
        childService.childMethod(); // 트랜잭션 없이 실행됨
    }
}

@Service
public class ChildService {
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void childMethod() {
        System.out.println("트랜잭션 없이 실행됨");
    }
}

1. parentMethod()가 실행되면서 트랜잭션이 시작됨

2. childMethod()가 실행될 때 부모 트랜잭션이 일시 정지되고 트랜잭션 없이 실행

  • 사용 예시
    • 데이터베이스 부하를 줄이기 위해 트랜잭션을 사용하지 않는 경우
    • 트랜잭션이 필요 없는 작업(ex. 외부 API 호출)

6. NEVER

  • 설명: 트랜잭션이 있으면 예외 발생, 없으면 정상 실행
  • 특징: 트랜잭션 없이 실행되어야 하는 메서드에서 실수로 트랜잭션이 걸리는 것을 방지
@Service
public class ChildService {
    @Transactional(propagation = Propagation.NEVER)
    public void childMethod() {
        System.out.println("트랜잭션이 있으면 예외 발생");
    }
}

1. parentMethod()가 트랜잭션을 시작한 상태에서 childMethod() 호출 시 예외 발생

  • 사용 예시
    • 트랜잭션을 사용하면 안 되는 메서드에서 실수를 방지하고 싶을 때

 

7. NESTED

  • 설명: 현재 트랜잭션이 있으면 중첩 트랜잭션을 생성하고, 없으면 REQUIRED처럼 동작
  • 특징: 내부 트랜잭션만 롤백할 수 있음
  • 사용 예시
    • 부모 트랜잭션이 실패하더라도 부분적으로 롤백해야 하는 경우
    • 부모 트랜잭션이 있으면 서브 트랜잭션을 생성(부모 롤백 시 자식도 롤백)
    • 자식이 롤백되어도 부모는 롤백되지 않음(Savepoint 활용)

 

반응형