Spring 기반 백엔드 개발을 하다 보면, 트랜잭션이 예상대로 롤백되지 않아 곤란한 상황을 겪은 적이 한 번쯤은 있을 것입니다.
이번 글에서는 Spring 트랜잭션의 예외 처리와 롤백 기준을 중심으로, 실무에서 반드시 알아야 할 핵심 개념과 동작 방식을 정리해봅니다.
Spring에서는 @Transactional 어노테이션을 통해 선언적 트랜잭션을 사용할 수 있습니다.
이때 어떤 예외가 발생하면 트랜잭션이 롤백되고, 어떤 예외는 커밋되는지 기준은 아래와 같습니다.
| 예외 종류 | 기본 동작 | 롤백 여부 |
|---|---|---|
Unchecked Exception (RuntimeException, Error) | 예외 발생 시 자동 전파 | 롤백 됨 ✅ |
Checked Exception (Exception 상속 중 RuntimeException 제외) | 기본적으로 커밋 | ❌ 롤백 안 됨 |
즉, 예외가
RuntimeException이상이면 롤백이 기본이지만,IOException,SQLException,ParseException같은 체크 예외는 자동 롤백되지 않습니다.
catch하면 롤백되지 않는다이건 실무에서 자주 발생하는 실수입니다.
예외가 발생해도 try-catch로 잡아버리고 throw하지 않으면, 트랜잭션은 롤백되지 않습니다.
@Transactional
public void process() {
try {
throw new IllegalStateException("에러 발생");
} catch (Exception e) {
// 예외를 삼킴 → 롤백되지 않음
log.warn("예외 발생: {}", e.getMessage());
}
}
위 코드에서는 IllegalStateException이 발생했지만, catch 블록에서 처리하고 다시 던지지 않기 때문에 트랜잭션은 커밋됩니다.
@Transactional
public void process() {
try {
...
} catch (RuntimeException e) {
log.error("에러", e);
throw e; // 다시 던지면 롤백됨
}
}
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@Transactional
public void process() {
try {
...
} catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
@Transactional의 rollbackFor 속성을 사용하면 됩니다.
@Transactional(rollbackFor = ParseException.class)
public void process() throws ParseException {
throw new ParseException("날짜 파싱 실패", 0);
}
컨테이너 관리 트랜잭션 (CMT)
→ RuntimeException은 기본 롤백.
→ Checked Exception은 @ApplicationException(rollback=true)로 지정 필요.
프로그래밍 방식 트랜잭션 제어 (manual)
→ begin() ~ commit() 혹은 rollback() 직접 호출
→ 예외 종류와 관계없이 개발자가 직접 처리
| 상황 | 롤백 여부 | 대응 방법 |
|---|---|---|
RuntimeException 발생 → 전파 | ✅ 롤백됨 | 기본 동작 |
Checked Exception 발생 | ❌ 커밋됨 | rollbackFor 명시 필요 |
try-catch 후 throw 없음 | ❌ 커밋됨 | throw하거나 setRollbackOnly() 호출 |
@Transactional 내부에서 private 메서드 호출 | ❌ 롤백 안 됨 | AOP 대상은 public 메서드 |
| 내부 메서드 호출 (this.method()) | ❌ 프록시 미적용 | 트랜잭션 분리 필요 시 별도 컴포넌트로 분리 |
트랜잭션의 롤백 기준은 단순히 예외의 종류뿐 아니라, 예외 전파 여부, AOP 적용 범위, 명시적 롤백 처리 여부 등 다양한 요소에 따라 달라집니다.
개발 초기엔 놓치기 쉬운 부분이지만, 실무에선 데이터 무결성과 직결되는 만큼 반드시 정확히 이해하고 있어야 합니다.
예외를 처리한다고 해서 트랜잭션도 자동으로 처리되는 건 아닙니다.
"예외가 트랜잭션을 벗어나야, 트랜잭션도 비로소 반응한다."