HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
✍🏻
Learnary (Learn - Diary)
/
🌪️
Soloving_Issue_Log
/
@OneToMany FetchJoin Pageing [JPA]

@OneToMany FetchJoin Pageing [JPA]

생성
May 27, 2023 07:00 AM
kindOf
JPA
Status
Done
1:N관게의 콜렉션 객체 페이징은 JPA에서 지원하지 않는다.코드 적용 내부 동작원리ManyToOne 관계에서의 페이징시 주의할점
 

1:N관게의 콜렉션 객체 페이징은 JPA에서 지원하지 않는다.

주요 대상 객체들 또는 테이블의 레코드들을 조회하기를 기대하지만 연관관계가 1:N이기 1쪽의 레코드들이 카테시안 곱으로 처리되어 페이징 할 수 없기 때문이다.
 
이와 반대로 ManyToOne 관계를 가지는 객체는 가능하다.
왜냐하면 N쪽의 레코드에서 1쪽과 결합하여도 N의 관계를 가지는레코드 1개와 1의 관계를 가지는 레코드가 1:1로 매핑될 수 있기 때문이다.
 

코드 적용 내부 동작원리

[User와 Post를 1:N의 관게로 맺어져 있는 상황이다.]
package com.example.jpa.user.repository; import static com.example.jpa.post.entity.QPostEntity.postEntity; import static com.example.jpa.user.entity.QUserEntity.userEntity; import java.util.List; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.support.PageableExecutionUtils; import com.example.jpa.user.entity.UserEntity; import com.querydsl.jpa.impl.JPAQuery; import com.querydsl.jpa.impl.JPAQueryFactory; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class CustomUserRepositoryImpl implements CustomUserRepository { private final JPAQueryFactory jpaQueryFactory; @Override public Page<UserEntity> findById(Long id, Pageable pageable) { List<UserEntity> contents = jpaQueryFactory.selectFrom(userEntity) .join(userEntity.posts, postEntity).fetchJoin() .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .where(userEntity.id.eq(id)) .fetch(); JPAQuery<Long> count = jpaQueryFactory.select(userEntity.count()) .from(userEntity) .join(userEntity.posts, postEntity) .where(userEntity.id.eq(id)); return PageableExecutionUtils.getPage(contents, pageable, count::fetchOne); } }
 
 
[모의 데이터로 User 5명이 존재하고 각 User 1명당 50개의 게시글을 삽입]
내부적으로 질의된 결과를 보면 50개의 게시글(사용자가 작성한 모든 게시판 개수) 만큼 User객체가 중복처리된 결과를 보여주고 있다.
이렇게 되면
notion image
 
컬렉션 페치 조인이 포함되고 있으며 쿼리를 생성하는데 실패하지 않았지만, 메모리에서 적용되고 있는 경고문이 날라오게 된다.
notion image
notion image
이 부분은 Hibernate Community 문서에도 적혀있다.
Fetch should be used together with setMaxResults() or setFirstResult(), as these operations are based on the result rows which usually contain duplicates for eager collection fetching, hence, the number of rows is not what you would expect.
User 테이블에서 일정 Limit 만큼 조회되기를 기대했는데 연관 객체들을 한번에 로딩하다보니 1의 레코드가 n개의 레코드와 합쳐줘서 JPA에서는 페이징 처리를 못하게 된다. 그러므로 이점을 꼭 알고 있어야 한다.

ManyToOne 관계에서의 페이징시 주의할점

불필요한 데이터인지를 확인해보는 것이 좋다.
 
post객체에서의 FetchJoin 하여 User참조를 보면 모두가 같은 User이다.
그렇기 때문에 20개의 Post에 같은 User들이 20개 나 있으니? 이 부분은 따로 조회하게되면 1개의 User만이 존재하고 웹 애플리케이션 자체 내의 메모리적 부분과 DB에서 웹 애플리케이션으로의 네트워크 전송량 측면이 줄어든다는 장점이 있다.
 
즉 중복데이터들이 너무 많음을 인지하고 N쪽에서의 페이징 처리는 FetchJoin보다 따로 조회하는것이 바람직 하다고 생각한다.