본문 바로가기
JPA/Querydsl

Querydsl 04 - 조인

by 킹차니 2022. 1. 17.

조인 - 기본 조인

/*
* 팀A에 소속된 모든 회원 조회
* */
@Test
void join() {
    List<Member> result = queryFactory
            .selectFrom(member)
            .join(member.team, team)//innerjoin, leftjoin, rightjoin 모두 가능
            .where(team.name.eq("teamA"))
            .fetch();

    for (Member m : result) System.out.println("m = " + m);
}

//테스트 성공
출력결과:
m = Member(id=3, username=member1, age=10)
m = Member(id=4, username=member2, age=20)

• join() , innerJoin() : 내부 조인(inner join)

 leftJoin() : left 외부 조인(left outer join)

 rightJoin() : rigth 외부 조인(rigth outer join)

 JPQL on과 성능 최적화를 위한 fetch 조인 제공 -> 다음 on 절에서 설명

 

/*
* 세타조인
* 회원의 이름과 팀의 이름이 같은 회원 조회 =
* */
@Test
void theta_join() {
    em.persist(new Member("teamA"));
    em.persist(new Member("teamA"));
    List<Member> result = queryFactory
            .select(member)
            .from(member, team)//from절에 나열
            .where(member.username.eq(team.name))
            .fetch();

    for (Member m : result) System.out.println("m = " + m);
}
//테스트 성공
출력결과:
m = Member(id=7, username=teamA, age=0)
m = Member(id=8, username=teamA, age=0)

 

 


조인 - on절

ON절을 활용한 조인 : 

1. 조인 대상 필터링

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

 

1. 조인 대상 필터링

/*
* 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조회, 회원은 모두 조회
* JPQL : select m, t from Member m left join m.team t on t.name = 'teamA'
* */
@Test
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);
    
}

출력결과:
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=10), null]
tuple = [Member(id=6, username=member4, age=20), null]

 

날라간 쿼리를 보면 아래와 같다.

select
            member0_.member_id as member_i1_1_0_,
            team1_.member_id as member_i1_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_ 
        left outer join
            team team1_ 
                on member0_.team_id=team1_.member_id 
                and (
                    team1_.name=?
                )

날아간 쿼리를 보면 on절 안에 and를 사용하여 데이터를 걸러내는 것을 알 수 있다.

 

위의 테스트를 일반 join으로 바꾸면 teamA가 아닌 member는 아예 가져오지 않는다.

@Test
void join_on_filtering2() {
    List<Tuple> result = queryFactory.select(member, team)
            .from(member)
            .join(member.team, team).on(team.name.eq("teamA"))//그냥 join으로 
            .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)]
참고: on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다. 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, 내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.

즉 위의 참고대로 join_on_filtering2을 where로 바꾸면 아래와 같다.

@Test
void join_where() {
    List<Tuple> result = queryFactory.select(member, team)
            .from(member)
            .join(member.team, team)
            .where(team.name.eq("teamA"))
            .fetch();

    for (Tuple tuple : result)
        System.out.println("tuple = " + tuple);

}

 

 

 

2. 연관관계가 없는 엔티티의 외부 조인

/*
 * 연관관계가 없는 엔티티 외부 조인
 * 회원의 이름과 팀의 이름이 같은 대상 외부 조인
 * */
@Test
void theta_on_no_relation() {
    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 t : result) System.out.println("t = " + t);
}

출력결과:
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=10), null]
t = [Member(id=6, username=member4, age=20), 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)]
t = [Member(id=9, username=teamC, age=0), null]

 

• 하이버네이트 5.1부터 on 을 사용해서 서로 관계가 없는 필드로 외부 조인하는 기능이 추가되었다. 물론 내부 조인도 가능하다.
주의: 문법을 잘 봐야 한다. leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어간다.

  - 일반조인: leftJoin(member.team, team)  -> 첫번째 파라미터의 key와 두번째 파라미터의 key로 매칭한다.

  - on조인: from(member).leftJoin(team).on(xxx)

 


 

조인 - 페치 조인

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

 

페치 조인 미적용 테스트와 적용 테스트를 보자.

@PersistenceUnit
EntityManagerFactory emf;

@Test
void no_fetchJoin() {
    em.flush();
    em.clear();
    Member findMember = queryFactory.selectFrom(member)
            .where(member.username.eq("member1"))
            .fetchOne();

    //페치 조인을 적용하지 않았으므로 team은 lazy 초기화로 아직 load되지 않은 상태이다.
    boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
    assertThat(loaded).as("페치조인 미적용").isFalse();
}

@Test
void yes_fetchJoin() {
    em.flush();
    em.clear();
    Member findMember = queryFactory.selectFrom(member)
            .join(member.team, team).fetchJoin()
            .where(member.username.eq("member1"))
            .fetchOne();

    //페치 조인을 적용하였으므로 Team도 같이 가져와서 team이 load됨
    boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
    assertThat(loaded).as("페치조인 적용").isTrue();
}

//두 개의 테스트 모두 성공

 

 

 

 

김영한님의 인프런 강의와 PDF를 바탕으로 정리하였습니다.