본문 바로가기
Java & SpringBoot

WebClient를 @Transactional과 사용할때 오류나면 롤백되는 이유

by 향각산 2025. 4. 8.

해당 형태롸 같은 코드 구조에서 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으로 처리해야 하는 케이스인지, 삭제해도 그만, 안해도 그만이라면 트랜잭션을 분리해야 하는것은 아닌지 코드 개선

 

그 외 해결법도 다양하게 존재하지만 구조적인 고민이 먼저인 것 같다.