[JPA] Collection 필드를 join 할 때 중복 데이터가 발생하는 이슈
이 포스팅은 아래 포스팅의 후속편입니다.
https://dogfootcrabfoot.tistory.com/43
팀 프로젝트에서 발견한 N+1 케이스...인 줄만 알았던 건에 대하여
국비 시절 팀프로젝트 코드를 살펴보다가 아무 생각없이 한 api에 요청을 보냈고.. 요청 하나 보냈을 뿐인데 7개씩 쿼리문이 날아가는 것을 발견하고 적잖이 충격을 받았다. 문제의 서비스는 한
dogfootcrabfoot.tistory.com
💣 문제 상황
에피소드 엔티티(NovelEpisode)와 댓글 엔티티(Comment)는 일대다 양방향 매핑 관계이다.
특정 소설의 모든 에피소드에 달린 댓글 리스트를 조회하기 위해 아래와 같은 쿼리 메소드를 작성했다.
@Query("select e from NovelEpisode e join fetch e.commentList c join fetch c.member where e.information.id = :novelInfoId")
List<NovelEpisode> findEpiWithCommentsAndMemberByNovelInfoId(Long novelInfoId);
위의 쿼리메소드를 사용해 특정 소설의 모든 에피소드 리스트를 조회하면 특정 상황에서 DB 테이블의 데이터가 중복되어 조회되는 문제가 발생!
🔍 원인
구글링을 통해 문제의 원인이 쿼리메소드를 정의한 jpql에서 사용된 fetch join에 있다는 걸 알게 되었다.
JPQL에서 NovelEpisode와 연관관계를 가진 Comment 객체의 collection 필드와 fetch join을 하고있다.
JPQL의 fetch join은 DB에 전달될 때 SQL의 inner join으로 번역이 되는데,
한 에피소드에 댓글이 여러 개인 경우 조인한 테이블에서 각 에피소드에 매칭된 댓글 수만큼 행이 생긴다.
DB 상에서 이건 중복 데이터가 아니지만 jpa 엔티티 입장에선 중복 데이터가 되는 것이다!
(위쪽의 log 이미지를 통해 확인 가능)
💡 해결
JPQL에 distinct 키워드를 추가하는 것으로 간단히 해결했다.
JPQL에서 distinct는 두 가지 가능이 있다.
1. JPQL을 SQL로 번역 시, SQL문에 distinct를 추가.
2. 애플리케이션에서 중복되는 엔티티 객체 제거.
JPQL이 SQL로 변환될 때 distinct를 추가하여 보내주긴 하지만 episode join comment를 한 결과값은 댓글 수만큼 나오기 때문에 이를 애플리케이션에서 같은 식별자를 가진 엔티티를 골라 중복제거 해준다.
@Query("select distinct e from NovelEpisode e join fetch e.commentList c join fetch c.member where e.information.id = :novelInfoId")
List<NovelEpisode> findEpiWithCommentsAndMemberByNovelInfoId(Long novelInfoId);
🔽 참고자료
https://velog.io/@nestour95/JPA-fetch-join%EA%B3%BC-distinct
https://devraphy.tistory.com/602
chat-gpt