본문 바로가기

Web Sever 개발과 CS 기초/스프링

Querydsl 의 JOIN (INNER, LEFT, RIGHT, THETA, FETCH) 사용법

관련 내용

<Spring Data JPA와 Querydsl 완전히 이해하기>

  • SpringDataJPA 이해와 사용법

Spring Data JPA 이해와 사용법

 

  • Querydsl 이해와 사용법

Querydsl 이해와 사용법

  • (현재글)Querydsl JOIN((INNER, LEFT, RIGHT, THETA, FETCH) 사용법

Querydsl 의 JOIN (INNER, LEFT, RIGHT, THETA, FETCH) 사용법

 

개요 목적

이 글에서는 Querydsl의 JOIN 기능을 설명한다.

각 조인들(INNER, LEFT 등)의 의미와, 사용 방법 그리고 사용 결과를 보면서 Querydsl의 JOIN 사용법을 익혀보자.

다양한 JOIN 기능을 알아보기 위한 DB 기본 세팅

JOIN을 설명할 때 코드 예시를 든다.

해당 예시에서 사용하는 데이터를 아래 그림에서 확인할 수 있다.

QueryDsl INNER JOIN

먼저 QueryDsl에서 innerJoin이란, 연관 관계가 있는 두 엔티티의 교집합을 구하는 것이다.

inner join의 코드 작성 방법과 어떤 쿼리를 날리는 지 확인해보자.

@Test
void innerJoin() {
    List<Tuple> result = queryFactory
        .select(member,team)
        .from(member)
        //innerjoin() 이렇게 써도 된다.
        .join(member.team, team)
        .fetch();
    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }
}
//결과 멤버 중에서 teamId
tuple = [Member(id=3, username=member1, age=10), Team(id=1, name=teamA)]
tuple = [Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]
tuple = [Member(id=5, username=member3, age=30), Team(id=2, name=teamB)]
tuple = [Member(id=6, username=member4, age=40), Team(id=2, name=teamB)]

JPQL 쿼리와 날라간 SQL 쿼리 확인하기

//JPQL 
select member1, team from Member member1 inner join member1.team as team

//SQL 쿼리
select
	member0_.member_id,
	team1_.team_id,
	member0_.age,
	member0_.team_id,
	member0_.username,
	team1_.name 
from
	member member0_ 
//inner join이 실행 된다.
inner join
	//FK로 연관관계가 있는 즉 member의 team_id와 team의 PK가 같은 교집합을 가져온다.
	team team1_ on member0_.team_id=team1_.team_id

on절 함께 사용하기

조인 대상을 필터링 할 수 있다.

외부조인이 아니라 내부조인(inner join)을 사용하면,where 절에서 필터링 하는 것과 기능이 동일하다. 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때,내부 조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.

ON절은 WHERE절 보다 순서 상으로 더 빠르고, outer join을 사용했을 때 확실히 확인이 가능하다. ON절의 조건으로 조인 되는 결과가 달라지고 where 조건 할 대상이 달라지기 때문이다.

@Test
void innerJoinOn() {
    List<Tuple> result = queryFactory
        .select(member,team)
        .from(member)
        //innerjoin() 이렇게 써도 된다.
        //join(조인 대상, 별칭으로 사용할 Q타입)
        .join(member.team, team)
        .on(team.name.eq("teamA"))
        .fetch();
    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }
}
//결과
tuple = [Member(id=3, username=member1, age=10), Team(id=1, name=teamA)]
tuple = [Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]

//SQL 쿼리
select
    member0_.member_id,
    team1_.team_id,
    member0_.age,
    member0_.team_id,
    member0_.username,
    team1_.name 
from
    member member0_ 
inner join
    team team1_ 
        on member0_.team_id=team1_.team_id 
        //on절에 and 조건을 붙여서 조인할 대상을 한번 필터링 한다.
        //LEFT RIGHT JOIN에서도 동일하게 작동한다.
        and (
            team1_.name=?
        )

//결과
tuple = [Member(id=3, username=member1, age=10), Team(id=1, name=teamA)]
tuple = [Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]

QueryDsl LEFT JOIN

left join이란, 왼쪽 테이블 즉, from(member)에 있는 테이블의 모든 데이터를 포함한다. team FK가 없더라도 데이터를 받아온다.

left join의 코드 작성 방법과 어떤 쿼리를 날리는 지 확인해보자.

@Test
void leftJoin() {
    List<Tuple> result = queryFactory
        .select(member,team)
        //주 조회 테이블 member가 left 테이블이 된다.
        .from(member)
        //leftjoin()을 사용한다. 같이 값을 가져올 대상 team right 테이블이다.
        .leftJoin(member.team, team)
        .fetch();
    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }
}
//결과
tuple = [Member(id=3, username=member1, age=10), Team(id=1, name=teamA)]
tuple = [Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]
tuple = [Member(id=5, username=member3, age=30), Team(id=2, name=teamB)]
tuple = [Member(id=6, username=member4, age=40), Team(id=2, name=teamB)]
//이너 조인과 다르게 팀과 접점이 없어도 모든 member 데이터를 들고온다.
tuple = [Member(id=7, username=member5, age=50), null]
tuple = [Member(id=8, username=member6, age=60), null]

JPQL 쿼리와 날라간 SQL 쿼리 확인하기

//JPQL 
select member1, team from Member member1 left join member1.team as team

//SQL 쿼리
select
    member0_.member_id,
    team1_.team_id,
    member0_.age,
    member0_.team_id,
    member0_.username,
    team1_.name
from
    member member0_ 
//left outer join이 날라간다. on에는 연관관계를 찾는 FK=PK 조건을 건다.
left outer join
    team team1_ 
        on member0_.team_id=team1_.team_id

QueryDsl RIGHT JOIN

right join이란, 오른 쪽 테이블 같이 값을 가져올 조인 대상 테이블(team)의 모든 데이터를 포함한다.

right join의 코드 작성 방법과 어떤 쿼리를 날리는 지 확인해보자.

@Test
void rightJoin() {
    List<Tuple> result = queryFactory
        .selectDistinct(member,team)
        .from(member)
        //rightJoin 사용
        .rightJoin(member.team, team)
        .fetch();
    for (Tuple tuple : result) {
        System.out.println("tuple = " + tuple);
    }
}
//결과
//left 테이블 값이 없는 team데이터도 전부 가져온다.
tuple = [null, Team(id=9, name=teamC)]
tuple = [null, Team(id=10, name=teamD)]
tuple = [Member(id=3, username=member1, age=10), Team(id=1, name=teamA)]
tuple = [Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]
tuple = [Member(id=5, username=member3, age=30), Team(id=2, name=teamB)]
tuple = [Member(id=6, username=member4, age=40), Team(id=2, name=teamB)]

JPQL 쿼리와 날라간 SQL 쿼리 확인하기

//JPQL 
select member1, team from Member member1 rightjoin member1.team as team

//SQL 쿼리
select
    member0_.member_id,
    team1_.team_id,
    member0_.age,
    member0_.team_id,
    member0_.username,
    team1_.name
from
    member member0_ 
//right outer join이 날라간다. on에는 연관관계를 찾는 FK=PK 조건을 건다.
right outer join
    team team1_ 
        on member0_.team_id=team1_.team_id

QueryDsl THETA JOIN - 막 조인

연관 관계가 없는 필드로 조인하는 방법이다.

기본적인 방법 코드

@Test
public void theta_join2() throws Exception {
    //테스트를 위해 데이터 추가
    em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));

    List<Member> result = queryFactory
        .select(member)
        //from 절에 조인할 team을 넣는다.
        .from(member, team)
        //wherer 절에
        .where(member.username.eq(team.name))
        .fetch();
    assertThat(result)
        .extracting("username")
        .containsExactly("teamA", "teamB");
}
//결과
tuple = [Member(id=11, username=teamA, age=0), Team(id=1, name=teamA)]
tuple = [Member(id=12, username=teamB, age=0), Team(id=2, name=teamB)]

이 방법은 레프트 라이트 아우터 조인을 하지 못한다. 조인 on절을 사용하면 세타조인인데도 아우터 조인을 할 수 있다. 그 방법에 대해서 알아보자.

연관관계 없는 엔티티 외부 조인 - 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(member.team, team) 이렇게가 아니라 아래처럼
        //작석하고 on에 세타 조인할 조건을 붙여주면된다.
        .leftJoin(team).on(member.username.eq(team.name))
        .fetch();
    for (Tuple tuple : result) {
        System.out.println("t=" + tuple);
    }
}
//결과
//아우터 조인 left테이블 모든 데이터 가져왔다.
t=[Member(id=3, username=member1, age=10), null]
t=[Member(id=4, username=member2, age=20), null]
t=[Member(id=5, username=member3, age=30), null]
t=[Member(id=6, username=member4, age=40), null]
t=[Member(id=7, username=member5, age=50), null]
t=[Member(id=8, username=member6, age=60), null]
t=[Member(id=11, username=teamA, age=0), Team(id=1, name=teamA)]
t=[Member(id=12, username=teamB, age=0), Team(id=2, name=teamB)]

FULL JOIN

왼쪽 오른쪽 데이터를 모두 가져오는 풀 조인은 지원하지 않는다.

Querydsl은 JPQL 스펙까지만 지원한다. JPQL 스펙이 Full outer join을 지원하지 않는다.

이런 경우 네이티브 쿼리를 사용해야 한다.

조인 - 페치 조인

페치 조인은 SQL에서 제공하는 기능은 아니다. SQL조인을 활용해서 연관된 엔티티를 SQL 한번에 조회하는 기능이다. 주로 성능 최적화에 사용하는 방법이다.

JPQL의 페치조인에 대한 자세한 이해는 아래 블로그 글에서 확인할 수 있다.

JPA JPQL, fetch join 중심으로 이해하기

코드를 보면서 페치 조인을 하는 방법을 알아보자.

페치 조인 미적용

@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();
}

페치 조인 적용

@Test
public void fetchJoinUse() throws Exception {
    em.flush();
    em.clear();
    Member findMember = queryFactory
        .selectFrom(member)
        //join 뒤에다가 fetchjoin()을 붙여준다. 
        .join(member.team, team).fetchJoin()
        .where(member.username.eq("member1"))
        .fetchOne();
    boolean loaded =
        emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
    assertThat(loaded).as("페치 조인 적용").isTrue();
}
//JPQL
select member1 from Member member1 inner join
fetch member1.team as team where member1.username = member1 

//SQL
//innerjoin을 통해서 team 정보까지 다 가져오는 것을 확인할 수 있다.
select 
		member0_.member_id,
    team1_.team_id,
    member0_.age,
    member0_.team_id,
    member0_.username,
    team1_.name
from
    member member0_ 
inner join
    team team1_ 
        on member0_.team_id=team1_.team_id 
where
    member0_.username=?

leftJoin() 등 조인 기능 뒤에 fetchJoin() 도 가능하다.