반응형
@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 활용)
반응형
'IT' 카테고리의 다른 글
[Java21] Virtual Threads (가상 스레드)란? (1) | 2025.04.30 |
---|---|
[DB 트랜잭션] @Transactional 속성(격리 수준 Isolation) (0) | 2025.03.04 |
[DB] 데이터베이스 기본 개념 (0) | 2025.02.25 |
[Spring Boot] Spring Boot 핵심 개념 10가지 (1) | 2025.02.24 |
[Auth] Spring Security AuthenticationException 처리 (2) | 2025.01.20 |