팀프로젝트 [Brewscape]
@Collection 타입 조회시 쿼리 다수 호출 문제 해결 (N+1 문제)
pobii
2025. 2. 9. 20:38
🤔문제 발생
- 한 엔티티에 일반 Collection타입(@ElementCollection) 필드값을 지정했을때, 저장/수정/조회시 쿼리가 다수 호출되는 문제 발생
- 조회 시: 모든 Review엔티티의 tag 값을 조회할 때, 아래 쿼리가 Review의 개수만큼 반복 실행됨 → N+1 문제
- 저장 시: Review의 tag를 저장할 때, 아래 쿼리가 tag의 개수만큼 반복 실행됨
🔍원인 분석
쿼리 분석
- 위의 쿼리를 보면 review_tags라는 테이블 때문에 저장/조회할때 쿼리가 많이 실행되는것을 알 수 있음
- review_tags 테이블은 자의로 생성한 기억이 없어서 어떤 이유로 생성되었는지 찾아보니, 아래 사진처럼 review 엔티티의 필드에 @CollectionTable로 지정되어 있었다.. 충분히 고민하지 않고 어노테이션을 적용하여 문제가 발생했던 것!
Collection의 매핑 테이블이 문제다!
- review_tags는 review와 tag간의 외래키를 저장하는 테이블임
- 테이블이 왜 생겼을까?
- Collection 타입은 고유 식별자가 없기 때문에, 아래 사진처럼 부모 엔티티의 식별자를 매핑한 테이블(review_tags)이 생성됨
- 이로 인해, Collection 타입을 저장할때마다 매핑 테이블에 식별자를 저장하는 쿼리가 실행되고, 조회할때마다 매핑테이블을 조회하는 쿼리가 실행됨 → N+1 문제
- 테이블이 왜 생겼을까?
⛏해결 과정
💡 @ElementCollection 필드 조회시 N+1 문제 해결과정
✅ 시도1 : FetchType.EAGER로 변경하기
- 기본적으로 @ElementCollection은 FetchType.LAZY로 작동하는데, 이를 EAGER로 변경하여 Collection값을 모두 로드한 후 조회
- 단점 : EAGER로 설정하면 부모 엔티티만 조회할 때도 자식인 Collection 값까지 같이 조회되므로 N+1 문제가 발생할 수 있음
✅ 시도2 : fetch join 사용하기
- EAGER 대신 LAZY + fetch join 조합 사용
- 단점 : QueryDSL을 사용할땐 @ElementCollection 필드의 Q객체를 자동으로 생성하지 않아join/fetch join을 사용할 수 없음 → 가독성을 위해 QueryDSL을 사용하여 구현해야 했으므로 보류
✅ 시도3 : @ElementCollection 타입 대신 Embedded 타입 사용하기
- 테이블을 따로 생성하지 않고 부모 엔티티 테이블 내에 데이터를 넣는 방식
- 아래 사진처럼 리스트에 들어갈 모든 경우의 수를 저장해야 한다.
- 장점 : 다른 방법보다 구현이 쉬움
- 단점 :
- 리스트 값이 무엇인지 예측할 수 없으면 사용하기 어려움
- 리스트값들을 변수로 바꾸는 것이기 때문에 리스트 값이 많을 경우 테이블 칼럼 수가 많아져 아래 사진처럼 보기 안좋음
✅ 시도4 : @ElementCollection 타입 대신 Entity 타입 사용하기
- 장점 : join을 사용하여 N+1문제 해결 가능
- 단점 :
- 방법3보다 구현 난이도 높음
- 저장 쿼리에서 쿼리 과다문제가 발생하므로 batch insert를 사용해야 함
💡 @ElementCollection 필드 저장시 쿼리 과다 문제 해결과정
✅ 시도 : batch insert 사용하기 → 실패
- 적용해보았지만 작동하지 않음
- 쿼리 로그를 통해 batch insert가 작동하지 않음을 확인
→ 서칭해보니 Hibernate는 @ElementCollection에 대해 Batch insert가 작동하지 않는다고 함
- 쿼리 로그를 통해 batch insert가 작동하지 않음을 확인
Why doesn't Hibernate batch inserts of fields annotated with @ElementCollection?
I have an @Entity containing a few @OneToMany relationships, but since they consist of collections of Enums, I'm using @ElementCollection. The entity has an id that gets generated at the database l...
stackoverflow.com
❓그럼 언제 @ElementCollection 타입을 사용하는게 좋은가?
- @ElementCollection은 ****저장/수정/조회 모든 면에서 쿼리가 추가 발생하므로 가급적 사용하지 않는게 좋음
- 엔티티보다 간단한 구현이 필요할 때 사용
- 저장/수정/조회가 빈번하지 않은 배열값일 때 사용
💎결론
- @ElementCollection 필드를 사용할 경우 저장/수정/조회 면에서 쿼리가 다량 발생했다. 이를 해결하기 위해 Entity 타입이나 Embedded 타입을 사용해야 한다.
- 해당 프로젝트에서는 리스트 값이 많아 Entity 타입을 선택했고, join을 사용하여 N+1 문제를 해결했다.