2023.12.06 과제하면서 만난 N+1(Spring Data Jpa)

GyungKu·2023년 12월 6일
2

유저의 장바구니 비우기 기능을 만들던 도중

public interface CartRepository extends JpaRepository<Cart, Long> {
	void deleteAllByUSer(User User);
}

deleteAllByUser를 통해서 장바구니를 초기화 시켰더니
where 문으로 일괄 삭제할 거라 생각했는데 예상과 달리 Cart의 개수만큼 delete 문이 나갔다.
deleteAll()은 for 문을 돌면서 id 값을 하나하나 확인하면서 지운다고 한다.

그래서 해결 방법을 모색해 봤더니 내가 알게 된 방법은 세 가지가 있다.

1. deleteAllInBatch(), deleteInBatch()

제거하는 코드

List<Cart> carts = cartRepository.findAllByUser(user);
cartRepository.deleteAllInBatch(carts);
// cartRepository.deleteInBatch(carts);

where에 or 문을 통해 제거한다.
Repository에는 따로 추가해 주지 않아도 된다.

쿼리

/* delete 
    from
        Cart x 
    where
        x = ?1 
        or x = ?2 
        or x = ?3 */ delete 
    from
        cart 
    where
        id=? 
        or id=? 
        or id=?

이런 식으로 쿼리가 나간다.

deleteInBatch 내부 코드

/**
	 * Deletes the given entities in a batch which means it will create a single query. This kind of operation leaves JPAs
	 * first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
	 * method.
	 *
	 * @param entities entities to be deleted. Must not be {@literal null}.
	 * @deprecated Use {@link #deleteAllInBatch(Iterable)} instead.
	 */
@Deprecated
	default void deleteInBatch(Iterable<T> entities) {
		deleteAllInBatch(entities);
	}

보면 deleteAllInBatch를 사용하고 있다.
그리고 Deprecated가 걸려있다. 고로, 둘 중엔 DeleteAllInBatch()를 사용하는 게 맞는 것 같다.
위의 설명에서도 그러라고 한다.

2. deleteAllByIdInBatch()

사용 코드

List<Cart> carts = cartRepository.findAllByUser(user);
cartRepository.deleteAllByIdInBatch(carts.stream().map(v -> v.getId()).toList());

쿼리

    /* delete 
    from
        Cart x 
    where
        id in :ids */ delete 
    from
        cart 
    where
        id in (?,?,?)

in 쿼리를 통해 나간다. 그러나 id값 list로 변환해 줘야 하는 번거로움이 있다.
위와 마찬가지로 Repository에는 따로 추가해 주지 않아도 된다.

3. jpql 활용하기

내가 선택한 방법이다.

Repository 코드

public interface CartRepository extends JpaRepository<Cart, Long> {
	@Modifying // select 외의 쿼리를 사용하기 위해서 필요함
	@Query(value = "delete from Cart c where c.user = :user")
	void clearCartByUser(User user);
}

사용 코드

cartRepository.clearCartByUser(user);

쿼리

    /* delete 
    from
        Cart c 
    where
        c.user = :user */ delete 
    from
        cart 
    where
        user_id=?

내가 생각한 쿼리로 where 문을 통해 제거한다.
그래서 나는 3번의 방식을 선택했다.

정리

deleteAllInBatch()

  • where에 or 쿼리를 통해 제거한다.

deleteAllByIdInBatch()

  • where에 in 쿼리를 통해 제거한다

jpql

  • 생각한 대로 where에 user(userId)를 통해 제거한다

정확히는 N+1문제라기보단 N문제인 것 같다.

1개의 댓글

comment-user-thumbnail
2023년 12월 14일

👍

답글 달기