조인 - 기본 조인
기본 조인
조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭으로 사용할 Q타입을 지정하면 됩니다.
join(조인 대상, 별칭으로 사용할 Q타입)
/**
* 팀 A에 소속된 모든 회원
*/
@Test
public void join() throws Exception {
// given
QMember member = QMember.member;
QTeam team = QTeam.team;
// when
List<Member> result = queryFactory
.selectFrom(member)
.join(member.team, team)
.where(team.name.eq("teamA"))
.fetch();
//then
assertThat(result)
.extracting("username")
.containsExactly("member1", "member2");
}
- join(), innerJoin() : 내부 조인
- leftJoin() : left 외부 조인(left outer join)
- rightJoin() : rigth 외부 조인(rigth outer join)
내부조인 외부조인 모두 사용할 수 있습니다.
세타 조인
만약 연관관계가 없는 필드로 조인을 하고 싶으면 어떻게 해야 할까요??
MySQL에서는 from 옆에 2개의 table을 적었습니다. Querydsl도 거의 비슷합니다.
/**
* 세타 조인(연관관계가 없는 필드로 조인)
* 회원의 이름이 팀 이름과 같은 회원 조회
*/
@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)
.where(member.username.eq(team.name))
.fetch();
assertThat(result)
.extracting("username")
.containsExactly("teamA", "teamB");
}
- 이렇게 연관관계가 없는 필드로 조인할 때는 from()에 집어 넣습니다.
- 이 예제는 멤버의 이름과 팀의 이름이 같은 것을 찾고 있습니다. 하지만 이 2개의 이름은 연관이 안 되어 있기 때문에 이렇게 세타조인을 해야합니다.
세타 조인의 단점은 외부 조인이 불가능합니다. 이것은 조인 on을 사용해서 해결 할 수 있습니다.
조인 - on 절
조인 대상 필터링
예를 들어 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회하는 쿼리를 짠다고 해보겠습니다.
@Test
public void join_on_filtering() throws Exception {
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);
}
}
이렇게 letfJoin을 할 경우 member는 모두 조회하고 teamA인 것만 도 조회할 수 있습니다.
만약 그냥 join을 하면 어떻게 될까? 아마 null이 안나올 것 입니다.
이렇게 null이 아닌 것만 뽑아옵니다.
강사님 추천
on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일합니다. 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용합니다!!
연관관계 없는 엔티티 외부 조인
위에서 연관관계가 없는 엔티티를 외부 조인할 때는 on으로 해결할 수 있다고 했습니다.
위와 똑같은 예시로 회원 이름과 팀의 이름이 같은 대상을 외부 조인 해보겠습니다.
@Test
public void join_on_no_relation()throws Exception {
em.persist(new Member("teamA"));
em.persist(new Member("teamB"));
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);
}
}
이렇게 from()에 2개를 하는 것이 아니라 leftJoin으로 team을 join 하면 됩니다. 그 후 on을 사용해 조건을 추가해주면 됩니다.
join(조인 대상, 별칭으로 사용할 Q타입) 이렇게 할 경우 자동으로 같은 id를 조인 하게 됩니다. 하지만 연관관계가 없는 것은 이게 불가능 하므로 위의 코드 처럼 사용합니다.
- 하이버네이트 5.1부터 on 을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가되었습니다. 물론 내 부 조인도 가능합니다.
- 문법을 잘 봐야 합니다. leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어갑니다.
- 일반조인 : leftJoin(member.team, team)
- on조인 : from(member).leftJoin(team).on(xxx)
조인 - 페치 조인
페치 조인은 프로젝트에서 효율문제를 해결할 때 굉장히 많이 사용했습니다. 페치 조인을 사용하면 연관된 엔티티의 내용을 모두 가져옵니다.
@PersistenceUnit
EntityManagerFactory emf;
@Test
public void fetchJoinNo() throws Exception {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("패치 조인 미적용").isFalse();
}
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
이 부분은 findMember.getTeam()이 조회 되었는지를 확인 합니다. fetJoin을 사용하지 않았으므로 false가 결과값 입니다.
member만 조회 한 것을 알 수 있습니다. 이것은 Team과 Member와의 관계가 LAZY이기 때문입니다. 만약 Team의 내용을 모두 가져오고 싶으면 fetch join을 사용하면 됩니다.
@Test
public void fetchJoinUse() throws Exception {
em.flush();
em.clear();
Member findMember = queryFactory
.selectFrom(member)
.join(member.team, team).fetchJoin()
.where(member.username.eq("member1"))
.fetchOne();
boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("패치 조인 적용").isTrue();
}
이렇게 fetchJoin을 뒤에 붙이는 것으로 fetchJoin을 실행 할 수 있습니다.
team까지 모두 조회한 것을 볼 수 있습니다.
출처
https://www.inflearn.com/course/querydsl-%EC%8B%A4%EC%A0%84/dashboard