티스토리 뷰
신규 프로젝트에 쿼리 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_ |
---|---|---|---|---|
1 | wndgus@gmail.com | 최중현 | pwd1 | joonghyun |
1 | wndgus@gmail.com | 최중현 | pwd1 | joonghyun |
1 | wndgus@gmail.com | 최중현 | pwd1 | joonghyun |
1 | wndgus@gmail.com | 최중현 | pwd1 | joonghyun |
2 | ekdo@gmail.com | 김다애 | pwd2 | dakim |
2 | ekdo@gmail.com | 김다애 | pwd2 | dakim |
3 | tlgn@gmail.com | 장시후 | pwd3 | jangsi |
3 | tlgn@gmail.com | 장시후 | pwd3 | jangsi |
그럼 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
- n+1
- jpa
- Limit
- SpringBoot
- Validation
- spring
- ec2
- QueryDSL
- query dsl
- Spring Boot
- orm
- spring-data-jpa
- error
- insert
- fetch join
- Jenkins
- 페치조인
- 개발
- JUnit
- 쿼리
- hibernate
- SonarQube
- Database
- fetchjoin
- Docker
- web
- DEMONIZE
- @Valid
- query
- gradle
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |