Querydsl - Join

HotFried·2024년 2월 25일
0

Querydsl

목록 보기
4/9

import static study.querydsl.entity.QTeam.team;
import static study.querydsl.entity.QMember.member;
로 선언해뒀기 때문에 이때 Q타입의 별칭은 team, member가 될 것이다.
뒤의 예제에서 계속 이용할 것이니 짚고 넘어간다.

기본 조인

join(조인 대상, 별칭으로 사용할 Q타입) 의 문법을 가진다.

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    /**
     * 팀A에 소속된 모든 회원
     */
    @Test
    public void join() throws Exception {
        QMember member = QMember.member;
        QTeam team = QTeam.team;

        List<Member> result = queryFactory
                .selectFrom(member)
                // 멤버와 팀을 조인한다.
                .join(member.team, team)
                .where(team.name.eq("teamA"))
                .fetch();

        assertThat(result).extracting("username")
    }
}
select member1
from Member member1
         inner join
     member1.team as team
where team.name = ?1

정상적으로 inner join쿼리가 나간 것을 확인할 수 있다.


세타 조인

from 절에 여러 엔티티를 선택해서 세타 조인을 실행한다.

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    /**
     * 세타 조인(연관 관계가 없는 필드로 조인)
     * 회원의 이름이 팀 이름과 같은 회원 조회
     */
    @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");
    }
}

On절

조인 대상 필터링

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

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

member와 team을 leftJoin을 실시했고, on절을 통해 team의 이름이teamA인 member, team을 select하는 querydsl이다.

 t=[Member(id=3, username=member1, age=10), Team(id=1, name=teamA)]
 t=[Member(id=4, username=member2, age=20), Team(id=1, name=teamA)]
 t=[Member(id=5, username=member3, age=30), null]
 t=[Member(id=6, username=member4, age=40), null]

leftJoinOn절에서 조건을 달아줫기 때문에 팀이 teamA의 이름을 갖지 않는 member들의 team은 null로 나온다.

SELECT m.*, t.*
FROM Member m
         LEFT JOIN Team t ON m.TEAM_ID = t.id and
                             t.name = 'teamA'

실행결과 쿼리가 querydsl과 동일하게 잘 나온 것을 확인할 수 있다.

innerJoin의 경우 On절은 where절과 동일한 효과를 갖는다.
따라서 On절의 경우 OuterJoin의 경우에만 사용하도록 하자.


연관관계 없는 엔티티 외부 조인

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @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("t=" + tuple);
        }
    }
}

member와 team을 leftJoin을 하는데 member의 이름이 team의 이름과 같다는 조건의 On절을 걸어주고 있다.
-> 모든 member가 출력이 되고, 조건이 맞으면 team의 이름이 출력될 것이고 조건이 맞지 않다면 null로 출력될 것이다.

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=teamA, age=0), Team(id=1, name=teamA)]
t=[Member(id=8, username=teamB, age=0), Team(id=2, name=teamB)]

정상적으로 결과가 출력되는 것을 확인할 수 있다.

SELECT m.*, t.*
FROM Member m
         LEFT JOIN Team t ON m.username = t.name

정상적으로 leftJoinOn절이 쿼리로 나가는 것을 확인할 수 있다.

일반 조인과 On조인의 차이

1. 일반 조인 leftJoin(member.team, team)
id가 매칭되는 것을 가져온다.

from
    member m1_0 
left join
    team t1_0 
        on t1_0.team_id=m1_0.team_id 
        and t1_0.name=m1_0.username

2. on 조인 from(member).leftJoin(team).on()
on 조건으로만 필터링 한다.
SQL을 보면 id 없이 이름으로만 매칭한다.

from
    member m1_0 
left join
    team t1_0 
        on m1_0.username=t1_0.name

Fetch Join

@SpringBootTest
@Transactional
public class QuerydslBasicTest {
    @Test
    public void fetchJoinUse() throws Exception {
        em.flush();
        em.clear();

        Member findMember = queryFactory
                .selectFrom(member)
                .join(member.team, team)
                // fetch join 적용
                .fetchJoin()
                .where(member.username.eq("member1"))
                .fetchOne();

        // 연관 관계가 같이 로딩된다.
        boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());

        assertThat(loaded).as("페치 조인 적용").isTrue();
    }
}

join뒤에 fetchJoin()을 붙여주면 지연로딩으로 설정된 필드도 함께 불러온다.

select member0_.member_id as member_i1_1_0_,
       team1_.team_id     as team_id1_2_1_,
       member0_.age       as age2_1_0_,
       member0_.team_id   as team_id4_1_0_,
       member0_.username  as username3_1_0_,
       team1_.name        as name2_2_1_
from member member0_
         inner join
     team team1_ on member0_.team_id = team1_.team_id
where member0_.username = ?

정상적으로 fetchJoin()이 된 것을 확인할 수 있다.


참고 :

실전! Querydsl

profile
꾸준하게

0개의 댓글