트러블 슈팅

JPA DELETE 쿼리 중복 문제 해결하기

빠르게 핵심만 2024. 9. 20. 00:22

발생한 문제

상품과 리뷰는 단방향으로 매핑되어 있으며, 리뷰 측에서 @ManyToOne으로 설정했습니다.

상품을 삭제할 때 관련된 리뷰들도 함께 삭제하고자 했으나, 상품에 연관된 리뷰가 여러 개일 경우 삭제 쿼리가 중복으로 발생하여 성능 문제가 발생했습니다.

Hibernate: 
    delete 
    from
        product_review 
    where
        product_review_id=?
Hibernate: 
    delete 
    from
        product_review 
    where
        product_review_id=?
Hibernate: 
    delete 
    from
        product_review 
    where
        product_review_id=?

리뷰가 1000개인 경우 1000개의 삭제 쿼리가 실행되어 성능 문제가 있었습니다. 이를 해결하기 위해 중복 쿼리를 방지하고 효율적인 삭제 방법을 찾아야 했습니다.

 

문제 해결 과정

1. JPA 기본 메서드 사용
먼저 JPA에서 제공하는 deleteAllBy 메서드를 사용해 보았습니다.

package com.multivendorshop.productservice.repository;

import com.multivendorshop.productservice.entity.ProductReview;
import org.springframework.data.jpa.repository.JpaRepository;

public interface ProductReviewRepository extends JpaRepository<ProductReview, Integer> {

    void deleteAllByProductId(int productId);
}

하지만 테스트 결과, 이 메서드는 리뷰를 하나씩 삭제하기 때문에 여전히 다수의 삭제 쿼리가 발생하는 문제가 있었습니다.

 

2. @Modifying과 @Query 사용
중복된 쿼리 문제를 해결하기 위해 @Modifying과 @Query를 사용하여 한 번의 쿼리로 모든 리뷰를 삭제하는 방법을 구현했습니다.

package com.multivendorshop.productservice.repository;

import com.multivendorshop.productservice.entity.ProductReview;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;

public interface ProductReviewRepository extends JpaRepository<ProductReview, Integer> {

    @Modifying
    @Query("DELETE FROM ProductReview pr WHERE pr.product.id = :productId")
    void deleteAllByProductId(int productId);
}

 

상품 삭제 로직은 다음과 같이 작성되었습니다.

    @Transactional
    public void deleteProduct(int productId) {
        // 상품을 조회하고, 존재하지 않으면 예외를 발생시킨다.
        Product product = productRepository.findById(productId)
                .orElseThrow(() -> new ProductNotFoundException(productId));

        // 해당 상품의 리뷰들을 삭제한다.
        productReviewRepository.deleteAllByProductId(productId);

        // 상품을 삭제한다.
        productRepository.delete(product);
    }

 

Hibernate 로그를 확인해보면, 리뷰를 한 번에 삭제하는 단일 쿼리가 실행되는 것을 확인할 수 있었습니다.

Hibernate: 
    delete pr1_0 
    from
        product_review pr1_0 
    where
        pr1_0.product_id=?

 

결론

기본 JPA 메서드 사용 시 단건 삭제만 지원되기 때문에 다수의 리뷰를 삭제할 때 중복 쿼리 문제가 발생했습니다. 이를 해결하기 위해 @Modifying과 @Query를 사용하여, 리뷰를 한 번에 삭제하는 방식으로 성능 문제를 해결할 수 있었습니다.