목차
- 트랜잭션이란?
- @Transactional 어노테이션 동작 방식
- @Transactional 속성
- @Transactional 전파 속성(propagation)
- 정리
1. 트랜잭션이란?
트랜잭션은 데이터베이스의 상태를 변화시키는 작업의 논리적 단위로서 원자성(Atomicity), 일관성(Consistency), 격리성(Isolation), 지속성(Durability)의 ACID 특성을 보장한다.
- Atomicity (원자성) - 트랜잭션의 모든 쿼리가 DB에 반영되거나, 모두 반영되지 않아야 한다.
- Consistency (일관성) - 트랜잭션 처리 결과는 항상 일관성이 있어야 한다.
- Isolation (격리성) - 서로 다른 트랜잭션을 서로의 연산에 개입할 수 없다.
- Durability (지속성) - 트랜잭션이 성공적으로 처리되었다면 결과는 영구 반영되어야 한다.
Spring에서는 트랜잭션 관리를 위해 어노테이션을 이용한 선언적 방식(@Transactional)을 주로 사용하며, 내부적으로는 AOP 기반으로 동작한다.
2. @Transactional 어노테이션 동작 방식
먼저 @Transactional의 동작 방식에 대해 알아보자. Spring의 @Transactional은 내부적으로 AOP(Aspect-Oriented Programming) 기반으로 동작하며, Spring은 이를 프록시 기반 AOP 방식으로 구현한다.
즉, 우리가 작성한 클래스의 실제 객체를 감싸는 프록시 객체를 만들어, 메서드 호출 전후로 트랜잭션을 시작하고 커밋하거나 롤백한다. 이 프록시 객체는 보통 싱글턴 스코프로 관리되며, 하나의 인스턴스만 생성되어 스프링 컨테이너에서 공유된다.
이때, 주의해야 할 문제가 있는데 바로 Self-Invocation 이다. 쉽게 이야기해서 자기 자신 내부 호출 시에 문제가 된다.
왜냐하면 같은 클래스 내부에서 @Transactional 메서드를 직접 호출할 경우, 프록시를 거치지 않고 자기 자신의 실제 메서드를 호출하게 된다. 이 경우, AOP 트랜잭션 로직이 적용되지 않기 때문에 트랜잭션이 동작하지 않는다.
다음 예시를 보자. validateOrder()는 같은 클래스 내에 있기 때문에 트랜잭션이 적용되지 않는다.
public class OrderService{
@Transactional
public void placeOrder(){
validateOrder(); // 프록시를 거치지 않아 트랜잭션이 적용되지 않음.
}
@Transactional
public void validateOrder(){
...
}
}
이를 해결하기 위해서는 다음 두 가지 방법을 사용할 수 있다.
- validateOrder() 메서드를 다른 서비스 클래스로 분리한다.
- ApplicationContext에서 자기 자신의 프록시를 꺼내 호출한다.
3. @Transactional 속성
이번엔 @Transactional 어노테이션이 제공하는 주요 속성들은 다음과 같다.
속성 | 설정 | 기본값 |
propagation | 트랜잭션 전파 방식 설정 | REQUIRED |
isolation | 트랜잭션 격리 수준 설정 | DEFAULT (DB 설정 따름) |
rollbackFor | 어떤 예외 발생 시 rollback 할지 지정 | RuntimeException, Error |
readOnly | 읽기 전용 트랜잭션 여부 | false |
timeout | 트랜잭션 수행 제한 시간 (초 단위) | -1 (제한 없음) |
위의 속성 중 대표적인 속성으로 propagation과 isolation이 있다. 이번 글에서는 전파 속성(propagation)에 대해 정리해 보고자 한다. isolation에 대한 내용은 다음 글을 참고하면 된다.
4. @Transactional 전파 속성(propagation)
트랜잭션 전파 속성은 이미 트랜잭션이 존재하는 상태에서 새로운 트랜잭션을 어떻게 처리할지를 결정하는 속성이다. 이는 중첩된 메서드 호출 시에 트랜잭션을 어떻게 다룰지에 대한 것으로 기존 트랜잭션에 참여할지, 새로 시작할지, 아니면 예외를 던질지를 결정할 수 있다.
스프링은 propagation 열거형을 통해 7가지 전파 속성을 제공한다. 하나씩 살펴보자.
(1) REQUIRED (Default)
REQUIRED는 기본 값으로 가장 많이 사용하는 설정이다. 트랜잭션이 필요한 것으로, 기존 트랜잭션(메서드 호출 시점 부모 트랜잭션)이 있으면 참여하고 없으면 트랜잭션을 새로 생성한다.
(2) REQUIRES_NEW
REQUIRES_NEW는 항상 새로운 트랜잭션이 필요한 것이다. 기존 트랜잭션이 있다면, 기존 트랜잭션을 보류시키고 새로운 트랜잭션을 생성한다. 기존 트랜잭션이 없다면 새로 트랜잭션을 시작한다.
(3) NESTED
NESTED는 부모 트랜잭션 안에 자식 트랜잭션을 생성한다. 따라서 기존 트랜잭션이 있으면, 중첩 트랜잭션을 만든다. 기존 트랜잭션이 없으면 새로운 트랜잭션을 생성한다.
중첩 트랜잭션은 내부에서 예외가 발생해 ROLLBACK 되더라도 외부 트랜잭션은 영향을 받지 않게 구성할 수 있다.
REQUIRES_NEW와 차이점은 다음과 같다.
- REQUIRES_NEW : 기존 트랜잭션이 있으면 기존 트랜잭션을 완전히 중단하고 새로운 트랜잭션 시작
- NESTED : 기존 트랜잭션 안에서 부분 롤백이 가능한 내부 작업 단위로 동작하며, 트랜잭션의 논리적 범위는 여전히 하나다. 즉, 외부 트랜잭션이 커밋되지 않으면 내부도 커밋되지 않는다.
(4) SUPPORTS
SUPPORTS는 트랜잭션이 존재하면 참여하고, 없으면 트랜잭션 없이 실행한다.
(5) NOT_SUPPORTED
NOT_SUPPORTED는 트랜잭션을 지원하지 않는다. 따라서 트랜잭션이 있으면 보류시키고 비트랜잭션 모드로 실행한다.
(6) MANDATORY
MANDATORY는 반드시 트랜잭션이 존재해야 한다. 기존 트랜잭션이 있으면 기존 트랜잭션에 참여한다. 만약 기존 트랜잭션이 없으면 IllegalTransactionStateException 예외를 던진다.
(7) NEVER
NEVER는 트랜잭션을 사용하지 않는 설정이다. 만약, 기존 트랜잭션이 없다면 트랜잭션 없이 진행된다. 기존 트랜잭션이 있다면 IllegalTransactionStateException 예외를 던진다.
5. 정리
데이터의 신뢰성과 일관성을 보장하기 위해 트랜잭션이 중요하며, 여러 작업들을 하나의 흐름으로 묶을 때 중요하다. 특히, 전파 속성은 비즈니스 로직의 흐름과 복구 전략을 설계하는데 영향을 준다. 따라서 적절한 전파 속성을 고려해야한다.
전파 속성 | 설명 | 부모 트랜잭션 없음 | 부모 트랜잭션 있음 | 사용 예시 및 특징 |
REQUIRED | 기본값. 트랜잭션이 있으면 참여, 없으면 새로 시작 | 새로 생성 | 기존 참여 | 일반적인 서비스 메서드에 가장 많이 사용됨 |
REQUIRES_NEW | 항상 새 트랜잭션 시작. 기존 트랜잭션은 중단 | 새로 생성 | 기존 중단 후 새로 시작 | 독립적으로 반드시 처리해야 하는 작업 |
NESTED | Savepoint 기반의 중첩 트랜잭션 생성. 예외 발생 시 내부만 롤백 | 새로 생성 | Savepoint 생성 | 부분 롤백이 필요한 경우, 부모 트랜잭션 커밋 전까지는 전체 롤백 가능 (JDBC만 지원) |
SUPPORTS | 트랜잭션 있으면 참여, 없으면 트랜잭션 없이 실행 | 트랜잭션 없음으로 실행 | 기존 참여 | 단순 조회 로직 등 유연한 트랜잭션 처리가 필요한 경우 |
NOT_SUPPORTED | 트랜잭션이 있어도 무시하고 트랜잭션 없이 실행 | 트랜잭션 없음으로 실행 | 기존 중단 | 트랜잭션에 묶이지 않아야 하는 작업 |
MANDATORY | 반드시 트랜잭션이 있어야 함. 없으면 예외 발생 | 예외 발생 | 기존 참여 | 항상 트랜잭션 내에서만 실행되어야 하는 내부 유틸성 로직 등 |
NEVER | 트랜잭션이 있으면 예외 발생. 반드시 트랜잭션 없이 실행 | 트랜잭션 없음으로 실행 | 예외 발생 | 트랜잭션에 얽히면 안되는 작업 |
'Spring Boot' 카테고리의 다른 글
[Spring] Gradle 핵심 정리 (0) | 2025.03.27 |
---|---|
[Spring] 효율적인 패키지 구조와 네이밍 컨벤션 (0) | 2025.03.27 |
[Spring] Spring Bean이란? (0) | 2025.03.06 |
[Spring] SOLID 원칙 (1) | 2025.02.06 |
[Spring] Resilience4j를 통한 Circuit Breaker 패턴 적용 - (2)적용 (0) | 2025.01.15 |