che01 님의 블로그
트랜잭션 - 롤백 정책, 전파, 이벤트 리스너 본문
트랜잭션 롤백 정책
트랜잭션에서 어떤 예외가 발생했을 때 롤백을 진행할지 결정하는 정책입니다.
예외 종류 복습
- 체크예외(Exception): 컴파일 시점에 처리해야 하는 예외
- 언체크예외(RuntimeException): 런타임에 발생하는 예외
기본 롤백 정책
스프링에서는 기본적으로 RuntimeException이 발생했을 때만 rollback을 진행합니다. 체크예외는 기본적으로 rollback하지 않습니다.
롤백 정책 설정
rollbackFor
- 특정 예외를 rollback 정책에 추가할 때 사용
- 체크예외도 rollback 대상으로 만들 수 있음
noRollbackFor
- 특정 예외를 rollback 정책에서 제거할 때 사용
- RuntimeException이어도 rollback하지 않도록 설정 가능
@Transactional(rollbackFor = Exception.class)
public void methodWithRollbackFor() {
// 체크예외도 rollback 대상
}
@Transactional(noRollbackFor = RuntimeException.class)
public void methodWithNoRollbackFor() {
// RuntimeException이어도 rollback하지 않음
}
트랜잭션 전파
이미 진행 중인 트랜잭션이 있을 때 새로운 트랜잭션을 어떻게 처리할지 결정하는 정책입니다.
주요 전파 정책
REQUIRED (기본값)
- 기존 트랜잭션이 있으면 참여하고 없으면 새로 시작
- 가장 일반적으로 사용되는 정책
REQUIRES_NEW
- 항상 새로운 트랜잭션을 시작
- 기존 진행 중인 트랜잭션은 잠시 보류
- 자식 트랜잭션이 완료되면 부모 트랜잭션 재개
SUPPORTS
- 이미 트랜잭션이 있으면 참여, 없으면 트랜잭션 없이 진행
NESTED
- REQUIRES_NEW와 비슷하지만 부모 트랜잭션이 rollback되면 자식도 함께 rollback
NEVER
- 트랜잭션이 존재하면 예외 발생
실습 시나리오: 수강신청
수강신청 과정에서 로깅 실패 시 전체 트랜잭션이 rollback되는 것이 적절한지 고민해봐야 합니다. 로깅은 부가 기능이므로 REQUIRES_NEW를 사용하여 별도 트랜잭션으로 처리하는 것이 좋습니다.
@Transactional
public void registerCourse() {
// 수강신청 로직
courseService.register();
// 로깅은 별도 트랜잭션으로 처리
loggingService.log();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log() {
// 로깅 로직
}
트랜잭션 이벤트 리스너
트랜잭션의 특정 시점에 이벤트를 처리할 수 있는 기능입니다. 성공 시에만 로깅을 남겨야 하는 요구사항에 유용합니다.
이벤트 리스너 종류
BEFORE_COMMIT
- 트랜잭션 커밋 전에 실행
AFTER_COMMIT
- 트랜잭션 커밋 후에 실행
- 성공적으로 완료된 후 처리할 작업에 적합
AFTER_ROLLBACK
- 트랜잭션이 롤백되었을 경우 실행
AFTER_COMPLETION
- 커밋 또는 롤백이 끝난 후 실행
구현 방법
- 서비스에 ApplicationEventPublisher 의존성 추가
- 이벤트 클래스 생성
- 이벤트 리스너 생성
- 이벤트 발행 로직 추가
@Service
public class CourseService {
private final ApplicationEventPublisher eventPublisher;
@Transactional
public void registerCourse() {
// 수강신청 로직
// 이벤트 발행
eventPublisher.publishEvent(new CourseRegisteredEvent());
}
}
@Component
public class CourseEventListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleCourseRegistered(CourseRegisteredEvent event) {
// 트랜잭션 성공 후 로깅
log.info("수강신청이 성공적으로 완료되었습니다.");
}
}
이벤트 리스너를 사용하면 트랜잭션의 결과에 따라 적절한 후처리를 할 수 있으며, 비즈니스 로직과 부가 기능을 깔끔하게 분리할 수 있습니다.