다음은 제가 실제로 이전 프로젝트에서 썼던 연관관계이고, delete쿼리에 대해서 신경쓰지 않고 설정한 부분입니다.
그리고 아래는 부모 객체인 Memo, 즉 게시글 객체를 삭제하는 매서드입니다.
위와 같은 형태에서는 Memo를 삭제 할 때 연관되어있는 Comment에 N+1번의 delete 쿼리가 나갑니다. 더하여 연관관계 없이 설정해두었던 Like(좋아요)객체들의 삭제 쿼리까지 하면 정말 너무 많은 쿼리가 발생하고 있습니다.
그리고 이 문제의 원인은, 부모 객체를 삭제 할 때 자식 객체를 한번에 삭제하지 못하고 일일이 for문을 돌면서 삭제해야 한다는 한계점 때문이었습니다. JPA에서 기본적으로 제공하는 부분이죠
그러나 실제 서비스를 생각 해 보면 delete쿼리가 몇개 나가는지는 굉장히 중요한 문제입니다.
만약, 게시글 하나에 댓글이 1만개 달려있다면? 게시글에 있는 좋아요는 2만개이고, 댓글의 좋아요 총합은 5만개라면?
위와 같은 경우 일일이 delete 쿼리를 날리면 DB의 부담이 너무 심합니다. 게시글 하나 삭제하기 위해 너무 많은 부담을 져야하죠.
그래서, 이번 프로젝트에는 같은 실수를 반복하지 않고자 delete 쿼리를 최소화하는 방법들을 검색했습니다. 이에 따라 정리합니다.
OnDeleteCascade 는 DB 테이블에서 FK로 연결된 row들을 한번에 지우는 방법입니다. 스프링 JPA 환경에서는 @OnDelete 어노테이션을 통해서 사용 가능합니다.
@OnDelete(action = OnDeleteAction.CASCADE)의 형태로 사용합니다.
위와 같이, FK를 가지는 자식 엔티티에 있는 부모 필드(즉, FK...) 에 설정합니다.
위와 같이 @OnDelete 어노테이션을 붙인다는 것은, 데이터 베이스 단계에서의 관리를 하겠다는 의미입니다. On delete cascade는 DDL(Data Definition Language)이며 테이블간의 연관관계를 DB에서 관리합니다.
위 같은 경우 N+1 문제를 바로 해결 할 수 있습니다. 부모 객체인 Memo가 삭제 될 시에 자식 객체인 Comments 들에 대해서 하나의 쿼리로 삭제가 일어납니다
JPA에서 기본 제공하는 cascade 기능들이 왜 자식 객체들을 하나하나 삭제할까를 생각해보면, 위와 같이 DB단계에서 직접적으로 관여하는 것은 DB의 무결성을 훼손할 가능성이 생깁니다. 즉, ondelete 설정으로 인해 DB에 있는 테이블의 참조 부분까지 모조리 삭제 할 가능성이 생긴다는 것입니다.
위와 같은 형태는 즉 설계자가 삭제의 모든 과정을 신경쓰고 관여해야 한다는 것이고, 제어 관계의 역전(IoC) / 객체 지향의 다형성 등의 원리에 부합되지 않습니다. 우리는 최대한 JPA가 제공하는 기능을 안전하게 사용하는 것이 더 나을 것입니다.
deleteAllInBatch 는 where 조건에 맞는 엔티티들을 한방에 날려주는 쿼리를 작성해줍니다. 사용은 다음과 같이 합니다(예시입니다)
위와 같은 형태로, deleteAllInBatch 로 코멘트들을 삭제하고!
또 다시 사용해서 Memo에 붙은 Likes들을 삭제하고! 와 같은 식입니다.
위 방법보다는 안전해 보이기는 합니다. 그러나 보시는 여러분들도 상당히 불편하실테고, 눈에 보이는 문제보다 쪼오끔 더 문제가 되는 부분이 있습니다.
JPA repository에서 제공하는 delete 매서드들은 기본적으로 delete 쿼리를 날리기 전에 조회를 먼저 실행합니다. 따라서 성능 이슈가 있을 수 있습니다.
그러면 어쩌라는건가요?
@Modifying 어노테이션과 @Query 어노테이션을 사용해 직접 딜리트 쿼리를 날리는 방법입니다.
이 방법이 제일 많이 쓰는 방법이며, 다음과 같은 장점을 가집니다.
public interface CategoryRepository extends JpaRepository<Category, Long> {
@Query("delete from Category c where c.seller.id = :sellerId")
@Modifying(clearAutomatically = true)
void deleteAllBySellerId(@Param("sellerId") Long sellerId);
}
위와 같은 형태로 사용합니다.
deleteAllInBatch / 직접 delete쿼리 날리기 등이 현실적으로 써봄 직 합니다. 그 중에서도 딜리트 쿼리를 직접 날리는 부분을 더 많이 고려해야 할 것입니다.
추가적으로, Batch라는 키워드도 공부해 볼만 한 것 같습니다.