1. @Transactional이란?
스프링에서 데이터베이스의 트랜잭션 처리를 위해 사용하는 어노테이션이다.
트랜잭션이란 한 번에 수행되어야 할 작업의 단위를 의미하고, 모두 성공하면 커밋, 하나라도 실패하면 롤백된다.
트랜잭셔널 어노테이션은 보통 http 요청에서 CRUD 기능에 따라 옵션을 설정하여 사용한다.
예를 들어, GET 요청은 조회 시 대부분 사용되기에 @Transactional(readOnly = true)를 사용한다.
데이터는 소중하기 때문에, 삭제되거나 잘못된 작업에 의해 롤백되지 않으면 심각한 오류를 초래할 수도 있다.
2. 롤백 조건 – 왜 런타임 익셉션만 롤백할까?
스프링의 기본 정책은 Unchecked Exception(즉, RuntimeException과 그 하위) 발생 시 자동 롤백하고,
Checked Exception(즉, Exception을 상속하지만 RuntimeException이 아닌 예외) 발생 시 롤백하지 않는다.
- 즉, throw new RuntimeException() → 롤백
- throw new Exception() → 기본적으로 롤백 안 함
이유
실무에서 RuntimeException은 예상 못 한 치명적인 상황(프로그래밍 실수, DB 제약 위반 등),
Checked Exception은 비즈니스적으로 처리할 수 있는 예외(예: 네트워크 실패, 특정 비즈니스 검증 실패)로 설계한 것이 기본이기 때문이다.
롤백이 필요한 실수 예시
@Service
public class OrderService {
@Transactional
public void saveOrder(Order order) {
orderRepository.save(order);
// 아래에서 런타임 예외 발생
if (order.getAmount() < 0) {
throw new IllegalArgumentException("금액 오류");
}
}
}
위 코드에서 IllegalArgumentException(런타임 익셉션)이 발생하면, DB에 저장된 것도 롤백된다(=실제로는 DB에 반영되지 않음)
Checked Exception 발생 시 롤백이 안되는 예시
@Transactional
public void process() throws IOException {
// 비즈니스 처리
throw new IOException("파일 오류");
}
이 경우에도 롤백이 안 됨 (별도 옵션 지정 필요)
롤백 조건 바꾸기 (실무에서 자주 씀)
실제로는 Checked Exception이 발생해도 롤백해야 하는 경우가 많다.
@Transactional(rollbackFor = Exception.class)
public void doSomething() throws Exception {
// ... 코드
throw new Exception("예외 발생");
}
(rollbackFor = Exception.class) 별도의 옵션을 넣어, 예외 부모 클래스의 예외가 터지면 롤백 시키는 경우로 사용되기도 한다.
실무에서 주의할 점 & 흔한 실수
- 트랜잭션 범위 확인
서비스 계층(Service)에 주로 적용, Repository/DAO에는 직접 붙이지 않는다. - self-invocation 문제
같은 클래스 내에서 트랜잭셔널 메서드 끼리 호출하면, 프록시가 적용 안 돼 트랜잭션이 동작하지 않음. - 예외를 try-catch 후 삼키거나, catch에서만 처리하고 다시 throw 안 하면 롤백되지 않는다.
@Transactional
public void doJob() {
try {
// ... 코드
} catch (Exception e) {
log.error("에러", e);
// throw e; // 이걸 해줘야 롤백된다!
}
}
결론 및 실무 팁
- 예상치 못한 에러, 반드시 롤백되어야 하는 로직에는 런타임 예외를 던짐.
- 비즈니스 검증 실패 등에서 롤백이 필요하면 커스텀 런타임 예외(예: InvalidOrderException extends RuntimeException)를 만들어 사용.
- Checked Exception에도 롤백이 필요하면 @Transactional(rollbackFor = Exception.class) 옵션 반드시 명시.
- 트랜잭션은 최대한 짧게! (트랜잭션 안에 네트워크 호출, 외부 API 호출 등은 넣지 않는 것이 원칙)
- 트랜잭션 전파, 격리 수준 등도 실무에서는 자주 커스터마이즈하니, 프로젝트에 맞게 옵션 확인 필수.
사실 공통/아키텍처 팀이 공통메서드 또는 예외처리를 만들어 사용하는 곳은 개발자가 따로 커스텀해서 만들진 않을 것이다.
그래서 만들어져 있는 예외에 따른 사용은 개발자가 어느정도 알고 있어야 예기치 못한 오류를 막는다...
그래서 나는 파일처리와 같은 다른 내용의 메서드가 아니면 RuntimeException과 Exception 에외처리로 감싸고 @Transactional(rollbackFor = Exception.class) 옵션을 지정했다.
'실무' 카테고리의 다른 글
| [FE] jQuery 반복문($.each) 사용 시 주의점 | 민민의 하드디스크 - 티스토리 (0) | 2025.05.21 |
|---|---|
| [Spring] 스프링배치 흐름 및 @Schedueld 어노테이션 사용 시 유의사항 | 민민의 하드디스크 - 티스토리 (0) | 2025.05.21 |
| [DB] CONNECT BY와 WITH RECURSIVE 계층형 데이터 조회 | 민민의 하드디스크 - 티스토리 (1) | 2025.03.23 |
| [Spring] 데이터 전달하기: Model, Map, DTO 비교와 최적 활용 | 민민의 하드디스크 - 티스토리 (0) | 2025.03.14 |
| [DB] 트랜잭션과 데이터베이스 락(Transction & DB Lock) | 민민의 하드디스크 - 티스토리 (0) | 2025.03.13 |