이슈해결

[JPA] Collection 필드를 join 할 때 중복 데이터가 발생하는 이슈

로저스현식 2023. 8. 10. 21:59

이 포스팅은 아래 포스팅의 후속편입니다.

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 테이블의 데이터가 중복되어 조회되는 문제가 발생!

실제 데이터
findEpiWithCommentsAndMemberByNovelInfoId()를 사용해 에피소드 리스트 조회하면
에피소드 여러 개가 중복되어 조회됨

🔍 원인

구글링을 통해 문제의 원인이 쿼리메소드를 정의한 jpql에서 사용된 fetch join에 있다는 걸 알게 되었다.

 

JPQL에서 NovelEpisode와 연관관계를 가진 Comment 객체의 collection 필드와 fetch join을 하고있다.

JPQL의 fetch join은 DB에 전달될 때 SQL의 inner join으로 번역이 되는데,

한 에피소드에 댓글이 여러 개인 경우 조인한 테이블에서 각 에피소드에 매칭된 댓글 수만큼 행이 생긴다.

DB 상에서 이건 중복 데이터가 아니지만 jpa 엔티티 입장에선 중복 데이터가 되는 것이다!

(위쪽의 log 이미지를 통해 확인 가능)

mysql에서 에피소드 테이블과 댓글 테이블을 조인한 테이블에서 소설 id가 2인 데이터를 조회한 결과

💡 해결

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