해당 형태롸 같은 코드 구조에서 Transaction silently rolled back because it has been marked as rollback-only 가 계속 발생하였다
@Transactional
public void deleteProdcut(DeleteRequestDto serviceDto) throws ApiException {
securityService.initSystemPrincipal();
Product product = getProductByBarcode(serviceDto.getBarcode());
try {
asyncService.updateProductSaleToStop(product.getBarcode());
if (product.isRelease()) {
return;
}
if (product.getProductNo() != null) {
productDeleteService.deleteProduct(product.getProductNo());
}
} catch (Exception e) {
throw new ApiException(e.getMessage());
} finally {
product.deleteProduct(securityService.getCurrentUserId());
}
}
ApiException은 checked Exception이라서 롤백되지 않아야하는데 왜 자꾸 이미 트랜잭션 롤백이 마크돼서 트랜잭션을 실패하는지 알 수 없음.
async 서비스의 문제는 아니고, 유일한 케이스는 producDeleteService
해당 서비스는 WebClient를 사용해서 판매중인 상품을 내리는 코드였다.
하지만 WebClient 오류로 다음처럼 checked로 캐치하고 있던 상황
@NotNull
private static <T> Mono<T> requestApi(Class<T> response, RequestBodySpec requestBodySpec) {
return requestBodySpec.retrieve()
.onStatus(HttpStatusCode::is4xxClientError, clientResponse ->
clientResponse.bodyToMono(Object.class)
.flatMap(error -> Mono.error(
new ApiException(error.toString(), error.toString())))
).bodyToMono(response);
}
ApiException이 checked exception인데 대체 어디서 runtime Exception이 발생하는지 찾아본 결과
webClient에서 오류가 발생하면 우선 RuntimeException을 상속받은 WebClientResponseException이 발생하고 그것을 내가 원하는 Exception으로 덮어씌여주는 것
실제 구현체를 타고 들어가면 다음과 같다.





해결방법은 정말 트랜잭션 범위가 적절한지 고민해보고, checked excpetion으로 처리해야 하는 케이스인지, 삭제해도 그만, 안해도 그만이라면 트랜잭션을 분리해야 하는것은 아닌지 코드 개선
그 외 해결법도 다양하게 존재하지만 구조적인 고민이 먼저인 것 같다.
'Java & SpringBoot' 카테고리의 다른 글
[Java] WebClient queryParam uri 인코딩 문제 (0) | 2024.10.17 |
---|---|
Repository 테스트 @AutoConfigureJsonTesters (2) | 2023.12.05 |
서블릿 필터 & 스프링 인터셉터 (0) | 2022.10.04 |
Java 객체, 클래스, 인스턴스의 차이 (0) | 2022.07.18 |
자바에서 효율적으로 Exception 커스텀하기 (0) | 2022.02.08 |