Spring Framework는 데이터 무결성을 보장하고 롤백 처리까지 자동화하는 선언적 트랜잭션 관리를 제공합니다. 그 중심에 있는 것이 바로 @Transactional
어노테이션입니다. 실무에서 자주 마주치는 트랜잭션 관련 문제와 함께, @Transactional의 핵심 개념과 활용법을 정리해 보았습니다.
1. 트랜잭션이란 무엇인가?
트랜잭션(Transaction)은 하나의 논리적 작업 단위를 의미합니다. 데이터베이스에서 여러 작업이 함께 수행되어야 할 때, 일부 작업만 완료되고 나머지가 실패하면 데이터의 일관성이 깨집니다. 그래서 다음 네 가지 특징을 만족해야 합니다 (ACID):
- Atomicity (원자성): 모두 성공하거나, 전부 실패해야 함
- Consistency (일관성): 일관된 상태로 유지되어야 함
- Isolation (고립성): 동시에 실행 중인 트랜잭션은 서로 간섭하지 않아야 함
- Durability (지속성): 커밋된 트랜잭션은 영구 반영되어야 함
2. @Transactional의 기본 개념
Spring에서는 트랜잭션을 선언적으로 처리할 수 있도록 @Transactional
어노테이션을 제공합니다. 이 어노테이션을 메서드 또는 클래스에 붙이면, 트랜잭션 시작 → 메서드 수행 → 커밋 또는 롤백 흐름이 자동으로 적용됩니다.
@Transactional
public void registerUser(User user) {
userRepository.save(user);
emailService.sendWelcomeMail(user); // 예외 발생 시 모두 롤백
}
클래스 vs 메서드 단위 적용
- 클래스에 적용: 모든 public 메서드에 적용
- 메서드에 적용: 해당 메서드에만 적용
3. 주요 속성 정리
속성 | 설명 |
---|---|
propagation | 기존 트랜잭션 존재 시 어떻게 처리할지 (기본: REQUIRED) |
isolation | 격리 수준 설정 (기본: DEFAULT → DB 기본값 사용) |
rollbackFor | 어떤 예외가 발생할 때 롤백할지 지정 (기본은 RuntimeException만) |
readOnly | 읽기 전용 여부 (쓰기 불가 트랜잭션) |
timeout | 제한 시간 초과 시 롤백 (초 단위) |
예시: 커스텀 롤백 조건
@Transactional(rollbackFor = {IOException.class})
public void saveWithIO() throws IOException {
...
}
4. 실무에서 자주 겪는 이슈
1) 내부 메서드 호출 시 트랜잭션 무시됨
동일 클래스 내에서 this.method()
방식으로 호출하면 프록시를 통하지 않아 @Transactional이 적용되지 않습니다. → Service를 분리하거나 self-injection 기법이 필요합니다.
2) 예외가 발생했는데 롤백되지 않음
기본적으로 RuntimeException만 롤백 대상입니다. 예외가 checked exception
인 경우 rollbackFor
를 명시해야 합니다.
3) readOnly = true인데 INSERT 수행
MySQL 등에서는 readOnly 설정이 강제 제한이 되지 않습니다. 하지만 Hibernate는 내부적으로 flush 생략 등의 최적화를 수행하므로 읽기 성능 개선에 도움을 줍니다.
5. Propagation 옵션 정리
옵션 | 설명 |
---|---|
REQUIRED | 기존 트랜잭션이 있으면 참여, 없으면 새로 생성 (기본값) |
REQUIRES_NEW | 기존 트랜잭션을 일시 중단하고 새 트랜잭션 생성 |
NESTED | Savepoint를 만들어 부분 롤백 가능 (JDBC 지원 필요) |
NOT_SUPPORTED | 트랜잭션 없이 실행 |
NEVER | 트랜잭션 존재 시 예외 발생 |
6. 실전 적용 팁
- 외부 API 호출은 가급적 트랜잭션 외부에서 실행 (rollback 영향 없음)
- 읽기 전용 쿼리에는
readOnly=true
로 최적화 - 복잡한 처리 로직은 트랜잭션 분리하여 재시도 전략 적용 (e.g. 결제 처리)
- 테스트 코드에도
@Transactional
사용하면 테스트 후 자동 롤백 가능
마무리
Spring에서의 트랜잭션 관리는 복잡한 로직을 안정적으로 처리할 수 있게 해주는 중요한 기초입니다. 하지만 @Transactional의 기본 동작 원리를 정확히 이해하지 못하면, 트랜잭션이 적용되지 않는 버그로 이어질 수 있습니다. 트랜잭션 동작 방식과 다양한 실전 이슈를 확실히 정리하고 넘어가시기 바랍니다.
'개발 > JAVA' 카테고리의 다른 글
[JAVA] Spring Boot와 PostgreSQL 연동하기 – 실전 가이드 (0) | 2025.10.18 |
---|---|
[JAVA] Spring Boot와 MySQL 연동하기 - 입문 가이드 (0) | 2025.10.17 |
[JAVA] N+1 문제와 해결 방법 – Fetch Join과 @EntityGraph로 해결하기 (0) | 2025.10.15 |
[JAVA] JPA Fetch 전략 완벽 가이드 – Lazy vs Eager 실무 적용법 (0) | 2025.10.14 |
[JAVA] Entity 연관관계 완전 정복 – 1:N, N:M, Cascade까지 실무 중심 정리 (0) | 2025.10.13 |