[JPA] FetchJoin과 limit

타미·2020년 10월 11일
0

OneToMany 저장 로직 확인

상황

Instagram(One) - InstagramPost(Many)
OneToMany

@Entity
public class Instagram {
    public String name;

    public Instagram(String name) {
        this.name = name;
    }
    
    @OneToMany
    public List<InstagramPost> instagramPosts = new ArrayList<>();

    public void addInstagramPost(InstagramPost instagramPost) {
        instagramPosts.add(instagramPost);
    }
	...
}

@Entity
public class InstagramPost {
    public String name;

    public InstagramPost(String name) {
        this.name = name;
    }
    ...

OneToMany에서 FetchJoin하면 중복된 One이 나온다.

상황: (Instagram 1개에 InstagramPost 10개) x 10
예상: (Instagram 1개에 InstagramPost 10개) x 10
실제: (Instagram 1개에 InstagramPost 10개) x 100

	
    public List<Instagram> findInstagramsFetch() {
        return jpaQueryFactory.selectFrom(instagram)
                .innerJoin(instagram.instagramPosts,instagramPost)
                .fetchJoin()
                .fetch();
    }
    
    @DisplayName("OneToMany FetchJoin 조회")
    @Test
    void test() {
        // data set up
        for (int i = 1; i <= 10; i++) {
            Instagram instagram = new Instagram(i + "번째 Instagram");
            for (int j = 1; j <= 10; j++) {
                InstagramPost instagramPost = instagramPostRepository.save(new InstagramPost(j + "번째 InstagramPost"));
                instagram.addInstagramPost(instagramPost);
            }
            instagramRepository.save(instagram);
        }


        List<Instagram> fetchByLimit = instagramQueryRepository.findInstagramsFetch();
        assertThat(fetchByLimit).hasSize(10); // 에러 발생!
    }


innerJoin해서 생기는 data row는 100개고, 그만큼 return해준다.
instagarm1,instagram1...10개
instagram2,instagram2...10개 이런 식으로 중복값이 나온다.

        assertThat(instagrams.get(0)).isEqualTo(instagrams.get(9)); // 통과
        assertThat(instagrams.get(10)).isEqualTo(instagrams.get(11)); // 통과

참고

OneToMany에서 FetchJoin+Limit하면 Full scan하고 One이 중복되지 않는다.

상황: (Instagram 1개에 InstagramPost 10개) * 10
예상: 위에서 One이 중복되었기 때문에 limit 3을 할 경우, 3개는 모두 중복된 Instagram이다.
(Instagram1, Instagram1, Instagram1)
결과: 중복되지 않고 제대로 limit 되었다.
(Instagram1, Instagram2, Instagram3)


    public List<Instagram> findInstagramsFetchByLimit(int limit) {
        return jpaQueryFactory.selectFrom(instagram)
                .innerJoin(instagram.instagramPosts, instagramPost)
                .fetchJoin()
                .limit(limit)
                .fetch();
    }
    
    @DisplayName("OneToMany를 fetchJoin할 때 limit를 걸어준다.")
    @Test
    void test() {
        // data set up
        for (int i = 1; i <= 10; i++) {
            Instagram instagram = new Instagram(i + "번째 Instagram");
            for (int j = 1; j <= 10; j++) {
                InstagramPost instagramPost = instagramPostRepository.save(new InstagramPost(j + "번째 InstagramPost"));
                instagram.addInstagramPost(instagramPost);
            }
            instagramRepository.save(instagram);
        }

        List<Instagram> fetchByLimit = instagramQueryRepository.findInstagramsFetchByLimit(3);
        assertThat(fetchByLimit).hasSize(3);
        assertThat(fetchByLimit.get(0).name).isEqualTo("1번째 Instagram");
        assertThat(fetchByLimit.get(0).instagramPosts).hasSize(10);
        assertThat(fetchByLimit.get(1).name).isEqualTo("2번째 Instagram");
        assertThat(fetchByLimit.get(1).instagramPosts).hasSize(10);
        assertThat(fetchByLimit.get(2).name).isEqualTo("3번째 Instagram");
        assertThat(fetchByLimit.get(2).instagramPosts).hasSize(10);
    }

3개만 가져오고 싶어도 full scan한 후 3개를 뽑는다.
🤔그런데 왜 instagram1, instagram1, instagram1이 아니라 instagram1, instagram2, instagram3 이렇게 나올까...

아무튼 !! queryDsl에서 limit를 걸면 full scan을 한다. 데이터가 많으면 의도치 않게 다 메모리에 올린다는 문제가 있다.
2020-10-12 01:36:19.779 WARN 12068 --- [ main] o.h.h.internal.ast.QueryTranslatorImpl : HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!

OneToMany에서 (innerJoin)EagerLoading+limit하면 중복된 One이 나온다.

상황: (Instagram 1개에 InstagramPost 10개) * 10
예상: FetchJoin했을 때 중복되지 않은 one이 나왔으니 중복되지 않는다.
(Instagram1, Instagram2, Instagram3)
결과: 중복된 One이 나온다.
(Instagram1, Instagram1, Instagram1)

    public List<Instagram> findInstagramsEagerWithLimit(int limit) {
        return jpaQueryFactory.selectFrom(instagram)
                .innerJoin(instagram.instagramPosts, instagramPost)
                .limit(limit)
                .fetch();
    }
    
            List<Instagram> instagrams = instagramQueryRepository.findInstagramsEagerWithLimit(3);

        assertThat(instagrams.get(0).name).isEqualTo("1번째 Instagram");
        assertThat(instagrams.get(1).name).isEqualTo("2번째 Instagram");
        assertThat(instagrams.get(2).name).isEqualTo("3번째 Instagram");

profile
IT's 호기심 천국

1개의 댓글

comment-user-thumbnail
2020년 10월 15일

좋은 정보네요~ 잘읽었습니다!

답글 달기