티스토리 뷰

신규 프로젝트에 쿼리 dsl 을 도입하고 삽질한 내용을 정리하고자 합니다.

블로그 코드는 GitHub 에서 볼 수 있습니다.


1. one to many 관계 join 시 중복제거 이슈.


USER TABLE (ONE) 3개의 데이터 존재

INSERT INTO USER (idx, user_id, password, name, email) VALUES (1, 'joonghyun', 'pwd1', '최중현', 'wndgus@gmail.com');
INSERT INTO USER (idx, user_id, password, name, email) VALUES (2, 'dakim', 'pwd2', '김다애', 'ekdo@gmail.com');
INSERT INTO USER (idx, user_id, password, name, email) VALUES (3, 'jangsi', 'pwd3', '장시후', 'tlgn@gmail.com');

ARTICLE TABLE (MANY) 8개의 데이터 존재

INSERT INTO ARTICLE (idx, user_idx, content, category) VALUES(1, 1, '내용1', '01');
INSERT INTO ARTICLE (idx, user_idx, content, category) VALUES(2, 1, '내용2', '01');
INSERT INTO ARTICLE (idx, user_idx, content, category) VALUES(3, 1, '내용3', '01');
INSERT INTO ARTICLE (idx, user_idx, content, category) VALUES(4, 1, '내용4', '01');
INSERT INTO ARTICLE (idx, user_idx, content, category) VALUES(5, 2, '내용5', '01');
INSERT INTO ARTICLE (idx, user_idx, content, category) VALUES(6, 2, '내용6', '01');
INSERT INTO ARTICLE (idx, user_idx, content, category) VALUES(7, 3, '내용7', '02');
INSERT INTO ARTICLE (idx, user_idx, content, category) VALUES(8, 3, '내용8', '02');

UserRepository

/**
* user left join article
* */
@Override
public List<User> findWithArticle() {
return this.queryFactory.select(user)
.from(user)
.leftJoin(user.articles, article)
.fetch();
}

findWithArticle() 실행시 생성되는 쿼리

    select

        user0_.idx as idx1_4_,

        user0_.email as email2_4_,

        user0_.name as name3_4_,

        user0_.password as password4_4_,

        user0_.user_id as user_id5_4_ 

    from

        user user0_ 

    left outer join

        article articles1_ 

            on user0_.idx=articles1_.user_idx



최초 에상되었던 리스트의 개수는 3개... 하지만 리턴된 리스트는 총 8개였습니다..!

@Test
public void user_left_join_article() {
List<User> users = userRepository.findWithArticle();
Assert.assertEquals(8, users.size());
}

여기에서 한번더 생각해보니 실제 실행되는 쿼리결과는 아래와 같습니다. 2개의 table 을 join 을 하니 당연한 결과였습니다..


IDX1_4_  EMAIL2_4_  NAME3_4_  PASSWORD4_4_  USER_ID5_4_  
1wndgus@gmail.com최중현pwd1joonghyun
1wndgus@gmail.com최중현pwd1joonghyun
1wndgus@gmail.com최중현pwd1joonghyun
1wndgus@gmail.com최중현pwd1joonghyun
2ekdo@gmail.com김다애pwd2dakim
2ekdo@gmail.com김다애pwd2dakim
3tlgn@gmail.com장시후pwd3jangsi
3tlgn@gmail.com장시후pwd3jangsi


그럼 fetch join 을 사용하면 처음에 제가 원했던 결과대로 3개의 USER 가 나올까 궁금하였습니다.

/**
* user left fetch join article
* */
@Override
public List<User> findWithArticle_fetchJoin() {
return this.queryFactory.select(user)
.from(user)
.leftJoin(user.articles, article)
.fetchJoin()
.fetch();
}

하지만 USER 는 총 8개!

(서로 다른 USER 는 3개이지만 총 8개의 리스트가 반환됩니다.)


결론은 중복제거는 직접 해야한다! 였습니다.


그럼 가장먼저 생각이 드는것은 selectDistinct 를 사용하는 것이였습니다.

/**
* user left join article
* selectDistinct 으로 중복제거
* */
@Override
public List<User> findWithArticle_selectDistinct() {
return this.queryFactory.selectDistinct(user)
.from(user)
.leftJoin(user.articles, article)
.fetch();
}

실행되는 쿼리는 

select

        distinct user0_.idx as idx1_4_,

        user0_.email as email2_4_,

        user0_.name as name3_4_,

        user0_.password as password4_4_,

        user0_.user_id as user_id5_4_ 

    from

        user user0_ 

    left outer join

        article articles1_ 

            on user0_.idx=articles1_.user_idx


결과는 USER 리스트는 3 !!

아 이제 되는구나 하고 프로젝트는 계속 진행되었고.. 슬슬 쿼리 성능튜닝에 들어가면서 모든 쿼리를 explain 해보게 되었습니다. (사용한 데이터베이스는 Mysql 입니다.)


select

        distinct user0_.idx as idx1_4_,

        user0_.email as email2_4_,

        user0_.name as name3_4_,

        user0_.password as password4_4_,

        user0_.user_id as user_id5_4_ 

    from

        user user0_ 

    left outer join

        article articles1_ 

            on user0_.idx=articles1_.user_idx


위 쿼리에 대해서 explain 을 해보니


Using temporary 가 보였습니다..!


이게 뭘까 하고 구글링을 진행하였고 가상테이블을 만든다는 내용이였습니다.

참고한 글 : http://meonggae.blogspot.kr/2016/11/mysql-explain-using-temporary-using.html 


따라서 selectDistinct 는 제거하기로 하였고 stream 을 사용하여 중복제거로 진행하였습니다!

(query dsl 에서 fetch() 시 반환되는 리스트는 null 이 오지 않으므로 NullPointException 은 발생하지 않는것으로 확인되었습니다)

/**
* user left join article
* stream 으로 중복제거
* */
@Override
public List<User> findWithArticle_streamDistinct() {
return this.queryFactory.select(user)
.from(user)
.leftJoin(user.articles, article)
.fetch().stream().distinct().collect(Collectors.toList());
}


@Test
public void select_distinct_stream_distinct_같은지확인(){
int selectDistinctSize = userRepository.findWithArticle_selectDistinct().size();
int streamDistinctSize = userRepository.findWithArticle_streamDistinct().size();
Assert.assertEquals(selectDistinctSize, streamDistinctSize);
}


결론

one to many 관계를 join 시 중복된 결과가 존재하며 중복제거를 꼭 query dsl 해결하지 않고, query dsl 로 부터 중복된결과데이터를 받은 후 중복제거를 해도 된다.


다음번엔 one to many fetch join 시 limit 가 안되는 상황에 대해서 정리하겠습니다!

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함