'아, 이거 실행시키면 시간좀 걸리겠는데..?'
유저 삭제 로직을 만들다가 든 생각이었다.
백준에서 문제풀다가 봤던. for문 반복.
List<Product> products = productRepository.findProductsByUserUid(uid);
for (Product product : products) {
cartRepository.deleteByProduct(product);
wishlistRepository.deleteByProduct(product);
reviewRepository.deleteByProduct(product);
ordersCartRepository.deleteByProduct(product);
}
User - Product으로 이어진 외래키 제약, 거기에 이어진 cart, ordersCart, review, wishList 테이블이 문제였고. 삭제하기 위해서 나는 이렇게 생각했다
'흠.. 그럼 그냥 리스트 다 뽑아와서 돌리면 되는거 아냐?
그렇게 완성된 n+1도 아니고 N×4라는 코드가 완성됐다.
유저 한명이 100개의 상품을 갖고 있다고 한다면, 내 쿼리문은 100번이 돌아가고,
헤비유저라서 1000개의 상품이었다면 1천번이 돌아가야한다..
하지만 서브쿼리문을 사용하여 한번에 삭제하는 쿼리문을 만든다면?
N+1 문제에는 fetch join을 쓰는게 일반적이지만, 내 경우에는 삭제하는것이기 때문에 단순히 '벌크 삭제' 쿼리문을 작성하는게 낫다. fetch join을 사용한다면 어느정도의 성능 향상은 있겠지만, 그 수준이 미미한 수준이다.
@Modifying
@Transactional
@Query("DELETE FROM Cart c WHERE c.product IN (SELECT p FROM Product p WHERE p.user.uid = :uid)")
void deleteAllByUserProducts(@Param("uid") Long uid);
서브쿼리문으로, 딜리트문 한번에 삭제되게 만들었다.
카트, 위시리스트, 리뷰, 오더스카트 리포지터리에 각각 이런식으로 문구를 적어줬다.
이후에는 간단하다.
cartRepository.deleteAllByUserProducts(uid);
wishlistRepository.deleteAllByUserProducts(uid);
reviewRepository.deleteAllByUserProducts(uid);
ordersCartRepository.deleteAllByUserProducts(uid);
이런식으로 N+1(사실은 N*4) 문제를 해결했다!