Spring Boot 트랜잭션 전파와 격리 수준 이해
Spring Boot의 트랜잭션 전파와 격리 수준을 개념부터 코드 예제까지 단계별로 이해하기 쉽게 정리한 기술 설명서
목차
개요
트랜잭션은 데이터 일관성과 무결성을 지키는 핵심 수단이다. Spring Boot에서는 선언적 트랜잭션 관리로 개발 편의성을 제공한다. 이 글은 트랜잭션 전파(Propagation)와 격리 수준(Isolation)의 개념을 쉽게 설명하고, 실무 적용에 도움이 되는 코드 예제를 제시한다. 초보자도 이해하기 쉬운 순서로 구성한다.
트랜잭션 전파(Propagation) 개념
전파는 메서드 호출 관계에서 트랜잭션이 어떻게 이어지는지를 정의한다. 전파 전략에 따라 새로운 트랜잭션을 생성하거나 기존 트랜잭션을 공유하거나, 트랜잭션 없이 실행하도록 제어한다. 대표적인 전파 속성은 다음과 같다.
주요 전파 유형
- REQUIRED: 현재 트랜잭션이 있으면 참여하고, 없으면 새로 생성한다.
- REQUIRES_NEW: 항상 새로운 트랜잭션을 생성하며, 기존 트랜잭션이 있으면 일시 중단한다.
- SUPPORTS: 트랜잭션이 있으면 참여하고, 없으면 트랜잭션 없이 실행한다.
- NOT_SUPPORTED: 트랜잭션이 있으면 일시 중단하고 트랜잭션 없이 실행한다.
- MANDATORY: 반드시 기존 트랜잭션이 있어야 하며, 없으면 예외가 발생한다.
- NEVER: 트랜잭션이 있으면 예외가 발생한다.
- NESTED: 저장점(savepoint)을 사용해 중첩 트랜잭션처럼 동작한다(데이터소스가 지원해야 함).
전파 예시 설명
예를 들어 상위 서비스가 REQUIRED로 트랜잭션을 열고, 하위 서비스가 REQUIRES_NEW라면 하위는 별도 트랜잭션이 되어 상위 롤백과 독립적으로 커밋될 수 있다. 반면 하위가 REQUIRED라면 같은 트랜잭션에 참여하므로 어느 한쪽에서 예외가 발생하면 전체가 롤백된다. 이러한 동작을 이해하면 트랜잭션 경계 설계에 도움된다.
전파 예제 코드
아래 예제는 간단한 서비스 두 개가 서로 호출할 때 전파 동작을 보여준다. spring boot 트랜잭션 전파 예제 관점에서 작성되었다.
package com.example.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OuterService {
private final InnerService innerService;
public OuterService(InnerService innerService) {
this.innerService = innerService;
}
@Transactional(propagation = Propagation.REQUIRED)
public void outerMethod() {
// DB 작업 A
innerService.innerMethod(); // innerMethod가 REQUIRES_NEW면 별도 트랜잭션
// DB 작업 B
}
}
@Service
public class InnerService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void innerMethod() {
// DB 작업 C
// 예외 발생 시 inner 트랜잭션만 롤백되고 outer는 영향받지 않을 수 있음
}
}
트랜잭션 격리 수준(Isolation) 개념
격리 수준은 동시에 실행되는 트랜잭션 간의 상호작용을 조절한다. 주로 읽기 일관성과 동시성 문제를 다룬다. 흔히 언급되는 문제는 다음과 같다.
- Dirty Read: 다른 트랜잭션의 아직 커밋되지 않은 데이터를 읽는 상황
- Non-Repeatable Read: 같은 쿼리를 두 번 실행했을 때 결과가 달라지는 상황
- Phantom Read: 같은 조건의 쿼리에서 레코드 수가 달라지는 상황
격리 수준 종류
- DEFAULT: DB의 기본 격리 수준을 사용한다.
- READ_UNCOMMITTED: Dirty Read 허용.
- READ_COMMITTED: Dirty Read 방지, Non-Repeatable Read 가능.
- REPEATABLE_READ: Non-Repeatable Read 방지, Phantom Read는 DB에 따라 다름.
- SERIALIZABLE: 가장 엄격한 수준으로 모든 동시성 문제를 방지하지만 성능 저하가 발생할 수 있다.
격리 수준 예제
transaction isolation spring boot 설정 샘플이다. 특정 메서드에 isolation 속성을 지정하면 해당 트랜잭션에만 적용된다.
package com.example.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Transactional;
@Service
public class AccountService {
@Transactional(isolation = Isolation.SERIALIZABLE)
public void transferMoney(Long fromId, Long toId, int amount) {
// 잔액 조회 및 차감, 입금 로직
// SERIALIZABLE로 설정하면 동시성 충돌을 최소화함
}
}
실무 적용 포인트
- 성능과 일관성 사이의 균형을 고려한다. 높은 격리 수준은 일관성을 제공하지만 동시성 성능에 영향이 있다.
- 트랜잭션 범위를 최소화한다. 필요 이상으로 넓게 잡으면 잠금 경쟁이 증가한다.
- 외부 호출이나 네트워크 I/O는 트랜잭션 바깥으로 빼는 것이 바람직하다.
- REQUIRES_NEW는 보상 트랜잭션이나 로깅 등에서 유용하다. 단, 복잡한 롤백 시나리오를 고려해야 한다.
- NESTED는 Savepoint를 지원하는 데이터소스에서 유용하다. MySQL InnoDB 등에서 동작 특성을 확인한다.
마무리
spring transaction propagation 설명과 transaction isolation spring boot 개념을 이해하면 트랜잭션 설계가 더 명확해진다. 실제 시스템에서는 데이터베이스 특성, 동시성 패턴, 성능 요구사항을 함께 고려해 적절한 전파 전략과 격리 수준을 선택하는 것이 핵심이다.