집합 함수
@Test
public void aggregation() {
List<Tuple> result = queryFactory
.select(
member.count(),
member.age.sum(),
member.age.avg(),
member.age.max(),
member.age.min()
)
.from(member)
.fetch();
Tuple tuple = result.get(0);
assertThat(tuple.get(member.count())).isEqualTo(4);
assertThat(tuple.get(member.age.sum())).isEqualTo(100);
assertThat(tuple.get(member.age.avg())).isEqualTo(25);
assertThat(tuple.get(member.age.max())).isEqualTo(40);
assertThat(tuple.get(member.age.min())).isEqualTo(10);
}
count(),sum(),avg(),max(),min()함수 제공- 결과는
Tuple로 반환된다. → 추후에 설명
GroupBy
@Test
public void group() throws Exception {
List<Tuple> result = queryFactory
.select(team.name, member.age.avg())
.from(member)
.join(member.team, team)
.groupBy(team.name)
.fetch();
Tuple teamA = result.get(0);
Tuple teamB = result.get(1);
assertThat(teamA.get(team.name)).isEqualTo("teamA");
assertThat(teamA.get(member.age.avg())).isEqualTo(15); //(10+20) / 2
assertThat(teamB.get(team.name)).isEqualTo("teamB");
assertThat(teamB.get(member.age.avg())).isEqualTo(35); //(30 + 40) / 2
}

위에서 having() 절도 추가 가능하다
조인
/*
* 팀 A에 소속된 모든 회원
* */
@Test
public void join() {
List<Member> result = queryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}

join(),innterJoin(): 내부 조인(innerJoin) 실행leftJoin(): left 외부 조인(left outer join)rightJoin(): right 외부 조인(right outer join)
세타 조인
@Test
public void theta_join() throws Exception {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
List<Member> result = queryFactory
.select(member)
.from(member, team) // 조인을 쓰지 않고 그냥 from에서 가져와서 붙여버린다.
.where(member.username.eq(team.name))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("teamA", "teamB");
}

- from 절에서 여러 엔티티를 넣어서 세타 조인한다.
- 외부 조인은 불가능 → 다음에 설명할 조인 on을 사용하면 외부 조인 가능
조인 - on 절
ON절을 활용한 조인을 사용하는 경우
- 조인 대상 필터링
- 연관관계 없는 엔티티 외부 조인
1. 조인 대상 필터링
- 회원과 팀을 조인하면서 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
/*
* 예) 회원과 팀을 조인하면서 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
* JPQL: select m, t from Member m left join m.team t on t.name = 'teamA' */
@Test
public void join_on_filtering() {
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(member.team, team).on(team.name.eq("teamA"))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}

- member 기준으로
leftJoin을 실행 하고, teamA인것만 조인을 시켰기 때문에, teamB인 애들은 조인이 되지않아 Team이 null로 출력되었다.
2. 연관관계 없는 엔티티 외부 조인
연관관계가 아닌, 필드의 값으로 조인을 하는 기능
/*
* 연관관계가 없는 엔티티 외부 조인
* 회원의 이름이 팀 이름과 같은 대상 외부 조인
* */
@Test
public void join_on_no_relation() throws Exception {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
em.persist(new Member("teamC"));
List<Tuple> result = queryFactory
.select(member, team)
.from(member)
.leftJoin(team).on(member.username.eq(team.name))
.fetch();
for (Tuple tuple : result) {
System.out.println("tuple = " + tuple);
}
}

leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어간다.
- 일반 조인:
leftJoin(member.team, team)→ id값으로 조인 시킨다. - on조인:
from(member).leftjoin(team).on(xxx)→ on절에 선언한 필드의 값으로만 조인시킨다.
페치 조인
페치 조인 미적용
지연로딩으로 Member, Team SQL 쿼리가 각각 실행된다.
@Test
public void fetchJoinNo() {
em.flush();
em.clear(); // 영속성 컨텍스트 초기화
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"))
.fetchOne();
// 영속성 커넥스트에 loading되었는지 확인
// member에 LazyLoading된 team은 loading되지 않아 false값이 기대된다.
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 미적용").isFalse();
}

→ member만 조회하는 SQL이 실행되어, team은 영속성 컨텍스트에 영속되지 않았다. → Team은 프록시 객체로만 조회됨
페치 조인 적용
즉시로딩으로 Member, Team SQL 쿼리 조인으로 한번에 조회
@Test
public void fetchJoinUse() {
em.flush();
em.clear(); // 영속성 컨텍스트 초기화
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
// 영속성 컨텍스트에 team이 loading되었는지 확인
// fetch조인을 통해서 연관된 엔티티를 가져온다. -> ture값이 기대됨.
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 적용").isTrue();
}

→ 즉시 로딩되어, 연관된 Team의 모든 필드를 같이 조인하여, 영속성 컨텍스트에 로딩 시킨다.
요약
- QueryDSL 에서의 기본 집합함수 제공기능과, GroupBy, Having기능을 확인
- JPA, JPQL에서 사용했던, 조인 절의 활용을 확인 할 수 있엇다.
- FetchJoin 관련하여 다시 한번 살펴 보았는데, LazyLoading 전략을 이용하면서 엔티티의 정규화 및 연관관계 설정을 하고, 그로인해 발생하는 JPA의 N+1문제를 페치 조인을 통해서 해결 할수 있다는 사실을 다시 한번 복습하였다.
참고
해당 글은 김영한님의 실전! Query DSL을 공부하며 작성한 글입니다.
https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84
실전! Querydsl| 김영한 - 인프런 강의
현재 평점 5.0점 수강생 15,897명인 강의를 만나보세요. Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요! Querydsl을 기초부터 실무활용까지 한번에 배울 수 있습니다., 단순한 기능 설명을
www.inflearn.com
