[SpringBoot] 트랜잭션 전파 속성 (Transaction propagation)
애플리케이션을 개발할 때 데이터의 일관성을 유지하고 안정적인 동작을 보호하기 위해 트랜잭션 관리가 필수적입니다.
트랜잭션은 데이터를 처리하는 동안 발생할 수 있는 오류나 시스템 장애에 대비해 작업을 `롤백(rollback)`하거나 `커밋(commit)`하는 중요한 역할을 합니다. `트랜잭션 전파(Transaction Propagation)`는 여러 개의 트랜잭션이 서로 어떻게 상호작용하고, 하나의 트랜잭션이 다른 트랜잭션에게 어떻게 영향을 미칠지 결정하는 중요한 개념입니다.
이번 포스팅에서는 트랜잭션 전파에서 각 속성들의 개념과 동작과정에 대해 알아보도록 하겠습니다.
트랜잭션 전파 속성(Transaction Propagation)이란?
Spring이 제공하는 선언적 트랜잭션(@Transactional)은 여러 트랜잭션을 묶어 하나의 큰 트랜잭션 경계를 설정할 수 있는 장점이 있습니다.
여기서 `트랜잭션 전파 속성(Propagation)`은 이미 트랜잭션이 진행 중일 때, 새로운 트랜잭션이 어떻게 처리될지를 결정하는 중요한 개념입니다.
전파 속성에 따라 기존의 트랜잭션에 참여하거나, 새로운 트랜잭션을 시작하거나, 에러를 발생시키는 등 다양한 행동을 선택할 수 있습니다. 이는 애플리케이션의 복잡한 비즈니스 로직을 처리하는데 유용하게 사용됩니다.
스프링 프레임워크의 `org.springframework.transaction.annotation 패키지에 있는 @Transactional` 어노테이션은 트랜잭션의 전파 레벨을 정할 수 있습니다.
// ex
@Transactional(propagation = Propagation.REQUIRED)
물리 트랜잭션과 논리 트랜잭션
트랜잭션은 데이터베이스에서 제공하는 기술이므로 커넥션 객체를 통해 처리합니다. 한 개의 트랜잭션을 사용한다는 것은 하나의 커넥션 객체를 사용한다는 것이고, 실제 데이터베이스의 트랜잭션을 사용한다는 점에서 물리 트랜잭션이라고도 합니다. 물리 트랜잭션은 실제 커넥션에 `롤백, 커밋`을 호출하여 해당 트랜잭션의 종료를 의미합니다.
동작 과정
- 물리 트랜잭션 시작 (START TRANSACTION)
- 물리 트랜잭션은 실제 데이터베이스에서 트랜잭션을 시작하는 단계입니다. 데이터베이스와 연결된 커넥션을 통해 트랜잭션이 시작됩니다.
- 로직 수행 → 물리 트랜잭션 생성 → DB (START TRANSACTION)
- 논리 트랜잭션 시작, 물리 트랜잭션 참여
- @Transactional이 적용된 로직이 호출되면 논리 트랜잭션이 시작됩니다.
- 논리 트랜잭션 → 물리 트랜잭션 참여 (이미 존재하는 물리 트랜잭션에 참여 혹은 새로운 물리 트랜잭션 생성 요청)
- 논리 트랜잭션 종료
- 논리 트랜잭션이 종료될 때, 그 안에 포함된 모든 물리 트랜잭션도 커밋하거나 롤백을 통해 종료되어야 합니다. 논리 트랜잭션이 성공적으로 커밋되면, 그에 속한 물리 트랜잭션도 커밋됩니다.
- 물리 트랜잭션 커밋
- 모든 논리 트랜잭션이 종료되면, 물리 트랜잭션은 DB에 실제 변경을 반영하기 위해 커밋됩니다. 이때, 물리 트랜잭션의 상태가 DB에 반영되고 트랜잭션이 종료됩니다. (하나의 논리 트랜잭션이라도 실패 시 롤백)
- 물리 트랜잭션 → DB (COMMIT)
- 하나의 트랜잭션 종료
- 논리 트랜잭션과 그에 속한 모든 물리 트랜잭션이 커밋되면 전체 트랜잭션이 종료됩니다.
`논리 트랜잭션`은 스프링이 관리하지만, `물리 트랜잭션`은 실제 데이터베이스와 연결되어 커밋, 롤백을 처리합니다.
- 물리 트랜잭션: 실제 데이터베이스에 적용되는 트랜잭션으로, 커넥션을 통해 `커밋, 롤백`하는 단위
- 논리 트랜잭션: 스프링이 트랜잭션 매니저를 통해 트랜잭션을 처리하는 단위
- 모든 `논리 트랜잭션이 커밋 되어야 트랜잭션이 커밋`됩니다.
- `하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션 또한 롤백`됩니다.
트랜잭션 전파 속성 옵션
// ex
@Transactional(propagation = Propagation.REQUIRED)
트랜잭션 전파 설정은 `@Transactional`의 옵션 `propagation`을 통해 설정이 가능합니다.
- REQUIRED
- REQUIRES_NEW
- NESTED
- MANDATORY
- SUPPORTS
- NOT_SUPPORTED
- NEVER
트랜잭션의 전파 레벨에는 위와 같이 7가지가 존재합니다. 각 전파 레벨에 대해 알아보도록 하겠습니다.
REQUIRED : default
REQUIRED는 스프링이 제공하는 기본(default) 전파 속성으로, 기본적으로 2개의 논리 트랜잭션을 묶어 1개의 물리 트랜잭션을 사용하는 것입니다.
REQUIRED 동작 과정
- 로직 수행 시작
- 애플리케이션에서 로직1을 호출합니다.
- 물리 트랜잭션 생성
- @Transactional(propagation = REQUIRED)가 적용된 로직2를 호출합니다.
- `로직1`의 논리 트랜잭션 참여
- 로직1은 생성된 물리 트랜잭션에 참여합니다.
- `로직1 → 로직2 호출`
- 로직1 내부에서 @Transactional(propagation = REQUIRED)가 적용된 로직2를 호출합니다.
- `로직2`의 논리 트랜잭션 참여
- `로직2는 새로운 논리 트랜잭션으로 간주`되지만, `로직1과 동일한 물리 트랜잭션`에 참여합니다.
- `로직2` 논리 트랜잭션 종료
- 로직2의 작업이 완료되면 논리 트랜잭션이 종료되며, 물리 트랜잭션은 그대로 유지됩니다.
- `로직1` 논리 트랜잭션 종료
- 로직1의 작업까지 완료되면 모든 논리 트랜잭션이 종료됩니다.
- 물리 트랜잭션 커밋
- 모든 논리 트랜잭션이 정상적으로 커밋예약상태일 시 커밋됩니다. 여기서 단 하나의 논리 트랜잭션이라도 실패 시 롤백됩니다.
- 물리 트랜잭션 → DB (COMMIT)
REQUIRES_NEW
REQUIRES_NEW는 외부 트랜잭션과 내부 트랜잭션을 완전히 분리하는 전파 속성입니다. 2개의 물리 트랜잭션이 사용되며, 각각 트랜잭션 별로 커밋과 롤백이 수행됩니다.
REQUIRES_NEW 동작 과정
- 로직 수행 시작
- 애플리케이션에서 로직1을 호출합니다.
- `로직1`의 물리 트랜잭션 시작, 논리 트랜잭션 참여
- 로직1의 물리 트랜잭션이 시작되고, 로직1의 논리 트랜잭션이 해당 트랜잭션에 참여합니다.
- `로직1 → 로직2 호출`
- 로직2 호출 시 REQUIRES_NEW 전파 속성으로 인해 새로운 물리 트랜잭션이 생성되고 로직2의 논리 트랜잭션은 새로운 물리 트랜잭션에 참여합니다.
- `로직2` 문제 발생
- 로직2의 논리 트랜잭션이 종료되고 그에 해당하는 물리 트랜잭션이 `롤백`됩니다. 이때, 로직2의 물리 트랜잭션은 독립적으로 처리되어 `로직1`의 트랜잭션에 영향을 주지 않습니다.
- `로직1` 종료
- 로직1의 논리 트랜잭션이 종료되고 그에 해당하는 물리 트랜잭션이 커밋됩니다.
이 속성은 `로직1`과 `로직2`의 트랜잭션이 독립적으로 처리되도록 보장합니다.
즉, `로직2`가 실패하더라도 `로직1`의 트랜잭션은 영향을 받지않아 `로직2`의 실패로 인한 `로직1`의 롤백은 진행되지 않습니다.
NESTED
NESTED 동작 과정
- 부모 트랜잭션 존재 시 중첩 트랜잭션 생성
- 부모 트랜잭션 존재 시, 새로운 중첩 트랜잭션을 생성합니다. 이 트랜잭션은 부모 트랜잭션의 일부로 취급되고 부모의 커밋, 롤백에 의존합니다.
- 만약 부모 트랜잭션이 없다면, 새로운 독립적인 트랜잭션을 시작합니다. 이때 부모 트랜잭션과 별개의 트랜잭션이 처리됩니다.
- 중첩 트랜잭션 종료 시 커밋
- 중첩 트랜잭션 완료 시, 커밋은 부모 트랜잭션의 끝에서 이루어집니다. 즉, 자식 트랜잭션이 종료되더라도 부모 트랜잭션이 커밋될 때까지 실제로 데이터베이스에 반영되지 않습니다.
- 중첩 트랜잭션 롤백 발생
- 중첩 트랜잭션 롤백 시, 부모 트랜잭션에는 영향을 주지 않습니다. 즉, 중첩 트랜잭션의 실패로 인해 부모 트랜잭션이 롤백되지는 않습니다. (중첩 트랜잭션 시작 전 시점으로 롤백, 부모 트랜잭션은 정상 작동)
- 부모 트랜잭션 롤백 발생
- 부모 트랜잭션 롤백 시, 모든 트랜잭션이 롤백됩니다.
MANDATORY
반드시 부모 트랜잭션 하에서만 수행이 가능합니다. 만약, 부모 트랜잭션이 존재하지 않으면 `IllegalTransactionStateException` 예외를 발생시킵니다.
SUPPORTS
- 부모 트랜잭션 존재 시 트랜잭션에 참여합니다.
- 부모 트랜잭션이 존재하지 않으면 트랜잭션 없이 수행됩니다.
NOT_SUPPORTED
- 부모 트랜잭션 존재 시 트랜잭션을 보류시키고 트랜잭션 없이 수행됩니다.
- 부모 트랜잭션이 존재하지 않으면 정상적으로 트랜잭션 없이 수행됩니다.
NEVER
- 어떠한 경우에도 트랜잭션을 생성하지 않습니다.
- 부모 트랜잭션 존재 시 `IlegalTransactionalStateException` 예외를 발생시킵니다.
마무리
트랜잭션 전파는 애플리케이션의 안정성과 일관성을 보장합니다. 특히, 분산 시스템이나 복잡한 비즈니스 로직을 처리할 때, 적절한 전파 속성을 선택하는 것이 중요합니다.
트랜잭션 전파 속성 | 설명 |
REQUIRED | 부모 트랜잭션이 있으면 참여, 없으면 새로운 트랜잭션 생성 |
REQUIRES_NEW | 부모 트랜잭션과 상관없이 항상 새로운 트랜잭션 생성 |
NESTED | 부모 트랜잭션이 있으면 중첩 트랜잭션 생성, 없으면 새로운 트랜잭션 생성 |
MANDATORY | 부모 트랜잭션이 있으면 참여, 없으면 예외 발생 |
SUPPORTS | 부모 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 실행 |
NOT_SUPPORTED | 부모 트랜잭션이 있어도 트랜잭션 없이 실행 |
NEVER | 트랜잭션을 생성하지 않으며, 부모 트랜잭션이 있으면 예외 발생 |
'Spring' 카테고리의 다른 글
[SpringBoot] Spring Docs + Swagger 적용하여 API 문서 자동화하기 (0) | 2025.02.20 |
---|---|
[SpringBoot] Scale-out 환경에서 발생하는 Scheduler 중복 실행 문제 Shedlock으로 해결하기 (0) | 2025.02.06 |
[SpringBoot] AWS S3 다중 이미지 파일 업로드 및 삭제 구현하기 (feat. MultipartFile) (2) | 2025.01.17 |
[SpringBoot] @Scheduled를 이용한 스케줄러 구현 (0) | 2025.01.13 |
[SpringBoot] AWS SES로 이메일 전송 기능 구현하기 (0) | 2025.01.12 |