-
@Transactional 롤백과 @TransactionalEventListener프로젝트/미디어 스트리밍 서버 프로젝트 2022. 10. 4. 00:01
개요
비동기 EventListener를 다루면서 예외처리를 하던 중 고민했던 일들을 적어보고자 합니다.
@Transactional을 사용하는 메서드 예시
@Transactional public void function(request: UploadRequest) { val registeredVideo = videoService.registerVideo(request) // 1. request DB 저장 applicationEventPublisher.publishEvent(); // 2. 이벤트 발생 throw IOException() //예외 발생 return registeredVideo //A 반환 }
로직을 간소화하면 다음과 같습니다.
이때 과연 중간에 throw IOException()을 넣으면 롤백이 발생할까요?
롤백이 발생하지 않고 그대로 DB에 값이 저장됩니다.
왜 예외가 발생했는데 롤백이 되지 않을까요?
@Override public boolean rollbackOn(Throwable ex) { return (ex instanceof RuntimeException || ex instanceof Error); }
Spring의 트랜잭션 처리하는 과정을 추적해보면 RuntimeException이거나 Error인 경우에만 롤백을 실행하는 것을 알 수 있습니다.
따라서 IOException()을 throw 했기 때문에 롤백이 일어나지 않습니다.
IOException()은 Exception을 상속받고 있으므로 RuntimeException이 아니기 때문입니다.
특히 코틀린에서는 CheckedException 처리를 강제화하지 않기 때문에 이 부분에 조심해야 할 것 같습니다.
이외에도 롤백되지 않는 경우들이 존재합니다.
@Transactional은 인스턴스에서 처음으로 호출하는 메서드나 클래스의 속성을 따라가므로 class안에 상위 메서드에 @Transactional이 없으면 하위에 선언되어 있더라도 전이되지 않습니다.
private 메서드일 경우 롤백되지 않습니다.
롤백을 수행해야 하는 상황에서 방법
rollbackOn 속성을 사용해서 exception을 추가해주면 됩니다.
@Transactional(rollbackOn = {Exception.class})
rollbackFor 분석
/** * Defines zero (0) or more exception {@linkplain Class classes}, which must be * subclasses of {@link Throwable}, indicating which exception types must cause * a transaction rollback. * <p>By default, a transaction will be rolled back on {@link RuntimeException} * and {@link Error} but not on checked exceptions (business exceptions). See * {@link org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable)} * for a detailed explanation. * <p>This is the preferred way to construct a rollback rule (in contrast to * {@link #rollbackForClassName}), matching the exception type, its subclasses, * and its nested classes. See the {@linkplain Transactional class-level javadocs} * for further details on rollback rule semantics and warnings regarding possible * unintentional matches. * @see #rollbackForClassName * @see org.springframework.transaction.interceptor.RollbackRuleAttribute#RollbackRuleAttribute(Class) * @see org.springframework.transaction.interceptor.DefaultTransactionAttribute#rollbackOn(Throwable) */ Class<? extends Throwable>[] rollbackFor() default {};
해석하여 요약하면 다음과 같습니다.
트랜잭션 롤백을 유발해야 하는 예외 유형을 나타내는 Throwable의 서브클래스여야 하는 예외 클래스를 0개(0) 이상 정의합니다.
기본적으로 런타임 예외와 오류에서는 트랜잭션이 롤백되지만 확인된 예외(비즈니스 예외)에서는 롤백되지 않습니다.자세한 설명은 org.springframework.transaction.interceptor.DefaultTransactionAttribute.rollbackOn(Throwable)을 참조하세요.
org.springframework.transaction.interceptor.DefaultTransactionAttribute.rollbackOn(Throwable)
The default behavior is as with EJB: rollback on unchecked exception (RuntimeException), assuming an unexpected outcome outside of any business rules. Additionally, we also attempt to rollback on Error which is clearly an unexpected outcome as well. By contrast, a checked exception is considered a business exception and therefore a regular expected outcome of the transactional business method, i.e. a kind of alternative return value which still allows for regular completion of resource operations. This is largely consistent with TransactionTemplate's default behavior, except that TransactionTemplate also rolls back on undeclared checked exceptions (a corner case). For declarative transactions, we expect checked exceptions to be intentionally declared as business exceptions, leading to a commit by default. See Also: org.springframework.transaction.support.TransactionTemplate.execute
uncheckde Exception(RuntimeException) 또는 Error가 발생하면 rollback합니다.
다시 돌아와서 이벤트는 롤백될까요?
@Transactional public void function(request: UploadRequest) { val registeredVideo = videoService.registerVideo(request) // 1. request DB 저장 applicationEventPublisher.publishEvent(); // 2. 이벤트 발생 throw RuntimeException() //예외 발생 return registeredVideo //A 반환 }
이제 RuntimeException()이 발생했기 때문에 DB에 저장된 값의 롤백은 수행되지만 event의 롤백은 수행되지 않습니다.
이때 @EventListener대신 @TransactionalEventListener를 사용해서 해결할 수 있습니다.
- AFTER_COMMIT (기본값) - 트랜잭션이 성공적으로 마무리(commit)됐을 때 이벤트 실행
- AFTER_ROLLBACK – 트랜잭션이 rollback 됐을 때 이벤트 실행
- AFTER_COMPLETION – 트랜잭션이 마무리 됬을 때(commit or rollback) 이벤트 실행
- BEFORE_COMMIT - 트랜잭션의 커밋 전에 이벤트 실행
출처
https://sabarada.tistory.com/188
https://ynzu-dev.tistory.com/entry/Spring-Transactional이-적용되지-않을-경우롤백이-안되는-이유
'프로젝트 > 미디어 스트리밍 서버 프로젝트' 카테고리의 다른 글
JPA 연관관계 Paging 최적화 (0) 2022.10.06 @TestConfiguration 설정하기 (0) 2022.10.05 @Embedded vs @OneToOne (0) 2022.10.03 동시성 문제를 해결하자 (3) 2022.09.30 배포 스크립트 작성하기 (0) 2022.09.29