최근에 프로젝트에서 개발자들이 배치 작업 중 에러가 발생해도 rollback이 되지 않는다는 문의가 들어왔다. 문제의 원인과 해결 방법을 찾아냈고, 그것을 기록해둘까 한다.
원인
코드를 살펴보니 배치에서는 예외를 catch만 하고 throw는 하지 않고 있었다. 온라인 서비스에서는 예외를 throw하면 상위 계층(Controller, @ControllerAdvice 등)에서 받아서 후처리할 수 있지만, 배치는 예외를 throw하면 Step이 실패 처리되며 Job 실행이 중단되고 예외를 받아서 후처리해줄 곳이 없기 때문이다.
해결 방법 & 예시 코드
롤백되지 않는 코드
@Slf4j
@Component
public class SampleTasklet implements Tasklet {
@Autowired
private UserRepository userRepository;
@Override
@Transactional
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
try {
User user = new User("beforeRollback");
userRepository.save(user); // DB insert
// 강제로 에러 발생
int error = 1 / 0;
} catch (Exception e) {
log.error("에러 발생: {}", e.getMessage());
// ❌ 여기서 단순히 로그만 찍으면 트랜잭션은 롤백되지 않음
}
return RepeatStatus.FINISHED;
}
}
위 코드에서 1/0 연산으로 예외가 발생하지만, catch 블록에서 예외를 다시 throw 하지 않았기 때문에 트랜잭션이 정상 종료된 것으로 인식되어 DB에 user 데이터가 그대로 insert 된다. 즉, rollback이 일어나지 않는다.
롤백 강제 지정한 코드(해결 방법)
@Slf4j
@Component
public class SampleTasklet implements Tasklet {
@Autowired
private UserRepository userRepository;
@Override
@Transactional
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
try {
User user = new User("beforeRollback");
userRepository.save(user); // DB insert
// 강제로 에러 발생
int error = 1 / 0;
} catch (Exception e) {
log.error("에러 발생: {}", e.getMessage());
// ✅ 현재 트랜잭션을 롤백 상태로 표시
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
return RepeatStatus.FINISHED;
}
}
우선 삽입된 코드를 풀어서 설명하자면,
- TransactionAspectSupport
- Spring 프레임워크에서 제공하는 유틸리티 클래스
- AOP 기반 트랜잭션 관리 기능을 지원하며, 현재 트랜잭션에 접근할 수 있게 해줌
- currentTransactionStatus()
- 현재 진행 중인 트랜잭션의 상태(TransactionStatus) 객체를 반환
- 이 객체를 통해 트랜잭션의 진행 여부, rollback-only 여부 등을 확인/제어할 수 있음
- setRollbackOnly()
- 트랜잭션 상태를 "rollback 전용"으로 표시
- 이렇게 표시되면 트랜잭션이 commit 시도가 되더라도 무조건 rollback 됨
때문에, 'setRollbackOnly()'를 호출하면 현재 진행 중인 트랜잭션이 rollback-only 상태로 바뀐다. 이후 트랜잭션 commit 시도가 들어오면 무조건 rollback 되고, 덕분에 throw를 하지 않고도 DB 변경 내역이 rollback 된다.
정리
- 단순히 catch 후 로그만 찍으면 rollback되지 않는다.
- 예외를 throw 하면 Step 자체가 실패 처리된다.
- rollback만 하고 Step은 성공 처리하고 싶을 때는 'TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()' 사용한다.
'공개 글' 카테고리의 다른 글
| 예외 처리(Exception Handling) (2) | 2025.11.14 |
|---|---|
| 세션(Session), 쿠키(Cookie), 토큰(Token), JWT 완벽 정리 (0) | 2025.10.28 |
| Reflection과 Class 객체 (5) | 2025.07.31 |
| static (2) | 2025.05.02 |
| 트랜잭션(Transaction) (0) | 2025.04.28 |