반응형
📌 트랜잭션 전파(Propagation)란?
트랜잭션 전파(Propagation)는 트랜잭션의 존재 여부와 관계를 정의합니다.
- 현재 트랜잭션이 있는 경우 그 트랜잭션에 참여할지,
- 새로 트랜잭션을 생성할지,
- 트랜잭션 없이 실행할지를 결정합니다.
Spring에서 제공하는 전파 속성은 @Transactional
어노테이션의 propagation
속성으로 설정할 수 있습니다.
📌 트랜잭션 전파와 격리 수준의 차이
전파(Propagation)
- 트랜잭션의 관계를 설정합니다.
- 예: 새로운 트랜잭션 생성, 기존 트랜잭션 참여 등.
격리 수준(Isolation Level)
- 동시에 실행되는 트랜잭션 간 데이터 접근 규칙을 설정합니다.
- 예: Dirty Read 방지, Repeatable Read 보장 등.
간단히: 전파는 트랜잭션의 관계, 격리 수준은 데이터 무결성을 관리.
📌 트랜잭션 전파 속성의 종류와 동작 방식
Spring은 7가지 전파 속성을 제공합니다. 각 속성의 동작 방식을 살펴봅니다.
속성 | 설명 |
REQUIRED |
(기본값) 기존 트랜잭션에 참여. 없으면 새로 생성. |
REQUIRES_NEW |
항상 새로운 트랜잭션 생성. 기존 트랜잭션은 일시 중단. |
SUPPORTS |
트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행. |
NOT_SUPPORTED |
트랜잭션 없이 실행. 현재 트랜잭션이 있으면 일시 중단. |
MANDATORY |
반드시 기존 트랜잭션에 참여. 없으면 예외 발생. |
NEVER |
트랜잭션이 있으면 예외 발생. 트랜잭션 없이 실행 |
NESTED |
부모 트랜잭션 내부에서 독립적인 롤백이 가능한 트랜잭션 생성. (Savepoint 기반) |
📌 주요 전파 속성의 상세 동작 방식과 예제
1️⃣ REQUIRED
전파 속성
- 기존 트랜잭션이 있으면 참여, 없으면 새 트랜잭션 생성.
- 기본값으로 설정되어 있으며, 가장 일반적으로 사용됩니다.
사용 예제: 주문 및 결제
- 주문과 결제는 동일한 트랜잭션 안에서 처리되어야 합니다.
코드
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void placeOrder() {
saveOrderDetails(); // 같은 트랜잭션
processPayment(); // 같은 트랜잭션
}
@Transactional(propagation = Propagation.REQUIRED)
public void saveOrderDetails() {
// 주문 정보 저장
}
@Transactional(propagation = Propagation.REQUIRED)
public void processPayment() {
// 결제 처리
}
}
결과
placeOrder
메서드에서 오류 발생 시, 모든 작업이 롤백됩니다.- 동일한 트랜잭션으로 처리됩니다.
2️⃣ REQUIRES_NEW
전파 속성
- 항상 새로운 트랜잭션 생성.
- 부모 트랜잭션은 일시 중단됩니다.
사용 예제: 주문 처리와 로그 저장
- 주문 처리와 로그 저장은 서로 독립적이어야 합니다.
코드
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void processOrder() {
saveOrderDetails(); // 부모 트랜잭션
saveAuditLog(); // 독립 트랜잭션
}
@Transactional(propagation = Propagation.REQUIRED)
public void saveOrderDetails() {
// 주문 데이터 저장
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditLog() {
// 로그 데이터 저장
}
}
결과
saveAuditLog
는 부모 트랜잭션과 독립적으로 커밋/롤백됩니다.saveOrderDetails
에서 예외가 발생해도, 로그는 저장됩니다.
3️⃣ SUPPORTS
전파 속성
- 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행.
사용 예제: 상품 조회
- 상품 조회 작업은 트랜잭션 없이도 실행 가능하지만, 트랜잭션이 있다면 참여합니다.
코드
@Service
public class ProductService {
@Transactional(propagation = Propagation.SUPPORTS)
public List<Product> getAllProducts() {
// 상품 조회
return productRepository.findAll();
}
}
결과
- 트랜잭션 컨텍스트 내에서 호출되면 트랜잭션에 참여합니다.
- 트랜잭션 컨텍스트가 없으면 트랜잭션 없이 실행됩니다.
4️⃣ NOT_SUPPORTED
전파 속성
- 트랜잭션 없이 실행하며, 현재 트랜잭션이 있으면 일시 중단됩니다.
사용 예제: 파일 저장
- 파일 저장 작업은 데이터베이스 트랜잭션과 분리되어야 합니다.
코드
@Service
public class FileService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void saveFile(File file) {
// 파일 저장 작업
}
}
결과
- 데이터베이스 트랜잭션과 무관하게 파일 저장 작업이 수행됩니다.
5️⃣ MANDATORY
전파 속성
- 반드시 기존 트랜잭션에 참여해야 하며, 없으면 예외 발생.
사용 예제: 주문 상태 업데이트
- 주문 상태를 업데이트하려면 반드시 주문 생성 트랜잭션 내에서 실행되어야 합니다.
코드
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void processOrder() {
updateOrderStatus(); // 기존 트랜잭션에 참여
}
@Transactional(propagation = Propagation.MANDATORY)
public void updateOrderStatus() {
// 주문 상태 업데이트
}
}
결과
processOrder
가 트랜잭션 없이 호출되면 예외 발생.- 트랜잭션 내에서 호출될 때만 정상 실행.
6️⃣ NEVER
전파 속성
- 트랜잭션이 있으면 예외 발생, 없으면 트랜잭션 없이 실행.
사용 예제: 캐싱 작업
- 캐싱 작업은 트랜잭션과 분리되어야 하며, 트랜잭션 내에서 호출되면 안 됩니다.
코드
@Service
public class CacheService {
@Transactional(propagation = Propagation.NEVER)
public void cacheData(Object data) {
// 캐싱 작업
}
}
결과
- 트랜잭션 내에서 호출되면 IllegalTransactionStateException 발생.
- 트랜잭션 없이 호출될 때만 실행.
7️⃣ NESTED
전파 속성
- 부모 트랜잭션 안에서 동작하며, Savepoint를 생성해 트랜잭션의 일부 작업만 롤백 가능합니다.
- 부모 트랜잭션은 유지되며, 자식 트랜잭션에서 발생한 오류는 Savepoint를 활용해 부분적으로 롤백됩니다.
📌 동작 원리
- 부모 트랜잭션이 시작되면, NESTED 전파를 가진 메서드가 호출될 때 Savepoint가 생성됩니다.
- 자식 트랜잭션에서 오류가 발생하면 Savepoint까지만 롤백되며, 부모 트랜잭션은 영향을 받지 않습니다.
- 부모 트랜잭션이 커밋되지 않으면 자식 트랜잭션도 커밋되지 않습니다.
📌 사용 예제: 주문과 배송 처리
상황
- 주문 데이터 저장과 배송 데이터 저장을 처리해야 합니다.
- 배송 처리 중 문제가 발생하면, 배송 데이터만 롤백되고 주문 데이터는 유지됩니다.
코드
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void processOrder() {
saveOrderDetails(); // 부모 트랜잭션
processShipping(); // Savepoint 생성 (NESTED)
}
@Transactional(propagation = Propagation.REQUIRED)
public void saveOrderDetails() {
// 주문 데이터 저장 (부모 트랜잭션)
System.out.println("Order details saved");
}
@Transactional(propagation = Propagation.NESTED)
public void processShipping() {
System.out.println("Processing shipping...");
if (someConditionFails()) {
throw new RuntimeException("Shipping failed!");
}
// 배송 데이터 저장
System.out.println("Shipping details saved");
}
private boolean someConditionFails() {
return true; // 테스트를 위해 항상 실패하도록 설정
}
}
📌 동작 흐름
processOrder()
호출- 부모 트랜잭션이 시작됩니다 (
Propagation.REQUIRED
). saveOrderDetails()
실행 → 주문 데이터 저장.
- 부모 트랜잭션이 시작됩니다 (
processShipping()
호출- NESTED 전파에 따라 Savepoint가 생성됩니다.
- 배송 데이터 처리 중 예외 발생(
throw new RuntimeException()
).
- 롤백
- 자식 트랜잭션(
processShipping
)이 Savepoint로 롤백됩니다. - 부모 트랜잭션(
processOrder
)은 유지되며 커밋 가능합니다.
- 자식 트랜잭션(
📌 실행 결과
saveOrderDetails()
에서 주문 정보가 저장됩니다.processShipping()
에서 예외가 발생해 배송 데이터 저장이 롤백됩니다.- 부모 트랜잭션은 성공적으로 커밋됩니다.
📌 결과 해석
장점
- 부모 트랜잭션을 유지하면서 자식 트랜잭션만 롤백 가능.
- 복잡한 작업에서 부분 롤백이 필요한 경우 유용.
적용 사례
- 주문은 유지해야 하지만, 배송 데이터를 별도로 롤백해야 하는 경우.
- 부모 작업의 성공 여부와 관계없이 하위 작업만 롤백해야 할 때.
📌 전파 속성으로 인해 전체 작업이 롤백되는 상황과 해결 방법
문제
- 부모 트랜잭션이 롤백되면, 참여 중인 모든 트랜잭션이 함께 롤백됩니다.
해결 방법
REQUIRES_NEW
를 사용해 부모 트랜잭션과 독립된 트랜잭션 생성.
예시 코드
@Transactional
public void parentTransaction() {
childTransaction(); // 부모 트랜잭션 참여
saveLog(); // 독립 트랜잭션
}
@Transactional(propagation = Propagation.REQUIRED)
public void childTransaction() {
throw new RuntimeException("Rollback!");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog() {
// 독립적으로 로그 저장
}
📌 독립적인 트랜잭션에서 예외 발생 시 처리 방법
문제
- 독립 트랜잭션에서 예외가 발생해도 부모 트랜잭션에 영향을 미치지 않아야 함.
해결 방법
REQUIRES_NEW
로 독립 트랜잭션 생성.- 부모 트랜잭션과 상관없이 커밋/롤백.
예시 코드
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveAuditLog() {
try {
// 로그 저장 작업
} catch (Exception e) {
// 예외 처리
}
}
📌 전파 속성으로 인해 새로운 트랜잭션을 만들지 못하는 상황
문제
- 클래스 내부 호출로 인해 전파 속성이 적용되지 않음.
해결 방법
- AopContext 사용
- 내부 호출 시 프록시 객체를 강제로 참조.
- 메서드 분리
- 호출 메서드를 별도 빈으로 이동하여 외부 호출이 가능하도록 설계.
🚀 요약
- 전파 속성
- 트랜잭션의 생성/참여 여부를 결정.
REQUIRED
,REQUIRES_NEW
,SUPPORTS
등 다양한 설정 제공.
- 주요 전파 속성
REQUIRED
: 기본값. 트랜잭션 참여 또는 생성.REQUIRES_NEW
: 새로운 독립 트랜잭션 생성.NESTED
: 부모 트랜잭션의 일부로 동작하며 Savepoint 생성.
- 전파 속성 문제와 해결 방법
- 부모 트랜잭션 롤백 문제는
REQUIRES_NEW
로 해결. - 내부 호출 문제는 AopContext나 빈 분리로 해결.
- 부모 트랜잭션 롤백 문제는
반응형
'Java' 카테고리의 다른 글
[JAVA] Queue와 Deque 무엇이 좋을까? (2) | 2024.12.27 |
---|---|
[JAVA] @Transactional 알아보기 Part.3 #격리(Isolation) (2) | 2024.12.26 |
[JAVA] @Transactional 알아보기 Part.1 #프록시와 트랜잭션 동작 원리 (0) | 2024.12.23 |
[JAVA] Spring 공통 모듈을 패키지화하고 GitHub Packages에 등록하는 방법 (0) | 2024.12.06 |
[JAVA] Java로 Excel 파일 생성 및 관리: Apache POI 사용법 (0) | 2024.11.26 |