@Transactional 전파/고립수준 이해
스프링 트랜잭션은 기본만 알아도 충분할 것 같지만, 실제로는 전파(Propagation)와 고립수준(Isolation)을 모르면 장애가 납니다. 본문은 자주 틀리는 경우를 중심으로 “왜” 그런지와 “어떻게” 해결하는지 코드를 통해 설명합니다.
전파(Propagation) 핵심만 정리
- REQUIRED(기본): 현재 트랜잭션이 있으면 합류, 없으면 새로 시작
- REQUIRES_NEW: 무조건 새 트랜잭션 시작(기존은 일시 정지)
- SUPPORTS/NOT_SUPPORTED: 있어도/없어도 수행, 또는 트랜잭션 없이 수행
- MANDATORY/NEVER: 반드시 필요/있으면 안 됨
- NESTED: 동일 트랜잭션 내 세이브포인트(JDBC/드라이버 지원 필요)
예시: 알림 실패는 롤백시키지 않기
@Service
@RequiredArgsConstructor
class OrderService {
private final PaymentService payment;
private final NotificationService notify;
@Transactional
public void place(OrderReq req) {
payment.pay(req); // REQUIRED
notify.asyncNotify(req); // 실패해도 주문은 커밋
}
}
@Service
class NotificationService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void asyncNotify(OrderReq req) {
// 외부 API 호출
}
}
REQUIRES_NEW로 별도 트랜잭션을 만들면, 알림 실패가 주문 트랜잭션을 더럽히지 않습니다.
고립수준(Isolation)
- DEFAULT: DB 기본(대부분 READ_COMMITTED)
- READ_COMMITTED: 커밋 데이터만 읽음(더티리드 방지)
- REPEATABLE_READ: 같은 트랜잭션에서 같은 행은 같은 값(팬텀리드는 DB마다 다름)
- SERIALIZABLE: 완전 직렬화(성능 비용 큼)
@Transactional(isolation = Isolation.REPEATABLE_READ)
public Order load(Long id) { ... }
롤백 규칙과 흔한 오해
- 런타임 예외에서만 자동 롤백. 체크 예외는
rollbackFor로 지정. - 예외를 잡고 넘어가면 롤백되지 않습니다. 로깅만 하고 던질지 결정하세요.
- 자기 호출(self-invocation)은 프록시를 우회 →
@Transactional미적용. 구조를 바꾸거나, 내부 호출도 인터페이스/별도 빈으로 분리.
NESTED를 사용할 때
@Transactional
public void importAll() {
for (Row row : rows) {
try {
importOne(row); // 실패해도 다음 row 계속
} catch (Exception e) { log.warn("row fail", e); }
}
}
@Transactional(propagation = Propagation.NESTED)
public void importOne(Row row) { ... }
NESTED는 세이브포인트가 필요한 배치 시 유용합니다(드라이버/TxManager 지원 필요).
읽기 전용과 flush
@Transactional(readOnly = true)
public List<Order> search(...) { ... }
readOnly는 힌트를 통해 쓰기 지연/더티 체킹을 최적화합니다. 검색 전용 서비스에 기본으로 설정하세요.
테스트 팁
- 전파/고립 조합은 통합 테스트로 확인(두 서비스 간 호출, 예외 전파 등).
- Tx 로그(
org.springframework.transaction)를 DEBUG로 켜고 경계 확인.
FAQ
Q. REQUIRES_NEW 남용하면?
A. 커넥션을 하나 더 점유하고, 전체 일관성이 깨질 수 있습니다. 코어 로직에는 사용을 자제.
Q. 글로벌 readOnly?
A. 조회 전용 모듈이면 AOP/프록시로 기본 readOnly 적용을 고려하세요.
👉 2편: 메서드명 쿼리 vs @Query vs QueryDSL 비교
'Java & Spring' 카테고리의 다른 글
| Jackson 직·역직렬화 어노테이션 핵심(@Json*) (0) | 2025.10.05 |
|---|---|
| 페이징 성능: 카운트 최적화·키셋 페이징 (0) | 2025.10.03 |
| 메서드명 쿼리 vs @Query vs QueryDSL 비교 (0) | 2025.10.02 |
| 연관관계 주인/지연로딩 N+1 체크리스트 (0) | 2025.10.02 |
| Lombok 안전 사용 규칙(@Builder/@Value/@With) (0) | 2025.10.01 |