BackEnd/JPA

[JPA] QueryDSL 기본문법 - 3

샤아이인 2022. 5. 14.

내가 공부한 것을 올리며, 중요한 단원은 저 자신도 곱씹어 볼 겸 상세히 기록하고 얕은 부분들은 가볍게 포스팅하겠습니다.

 

8. 기본 Join

조인의 기본 문법은 첫 번째 파라미터에 조인 대상을 지정하고, 두 번째 파라미터에 별칭(alias)으로 사용할 Q 타입을 지정하면 된다.

join(조인 대상, 별칭으로 사용할 Q타입)

테스트 코드를 통해서 Join을 알아보자.

@DisplayName("팀 A에 소속된 모든 회원 찾기")
@Test
public void join_test() {
    List<Member> result = queryFactory
            .selectFrom(member)
            .join(member.team, team)
            .where(team.name.eq("teamA"))
            .fetch();

    assertThat(result).extracting("username")
            .containsExactly("member1", "member2");
}

위와 같이 join절에 join 대상과 별칭을 넘겨주면 된다.

참고로 별칭에 있는 team은 QTeam.team이다. static import 되어있는 것 이다.

 

생성된 SQL문은 다음과 같다.

select
    member0_.member_id as member_i1_1_,
    member0_.age as age2_1_,
    member0_.team_id as team_id4_1_,
    member0_.username as username3_1_ 
from member member0_ 
inner join team team1_ 
on member0_.team_id=team1_.team_id 
where team1_.name=?

정상적으로 inner join이 진행되었다.

  • join() , innerJoin() : 내부 조인(inner join)
  • leftJoin() : left 외부 조인(left outer join)
  • rightJoin() : rigth 외부 조인(rigth outer join)
  • JPQL의 on과 성능 최적화를 위한 fetch 조인 제공

또한 연관관계가 없는 Entity 간에 join 또한 가능하다. 이를 세타조인이라 부른다.

(다만 이름이 왜 세타조인 인지 모르겠다? 내가 알고있는 세타조인과 의미가 다르다.

다음 기능을 사실상 카티션곱 을 한후 where로 걸러낸것 아닌가?)

 

우선 이름이 teamA, teamB, teamC 인 회원 3명을 추가 등록하자. (이름이 약간 억지가...)

/**
 * 회원의 이름이 팀 이름과 같은 회원을 조회
 */
@Test
public void theta_join() {
    em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));
    em.persist(new Member("teamC"));

    List<Member> result = queryFactory
            .select(member)
            .from(member, team)
            .where(member.username.eq(team.name))
            .fetch();

    assertThat(result).extracting("username")
            .containsExactly("teamA", "teamB");
}

from 절에 여러 엔티티를 선택해서 세타 조인이 가능하다. (원래 SQL에서도 from 절에 여러 테이블을 명시해주면 카티션곱을 해준다)

또한 연관관계가 없기 때문에 from(member, member.team) 이 아니라, from(member, team) 이다!

여기까지 하면 member 와 team 간의 카티션곱이 완료되었다. 이후 where 절을 통해 필터링을 하게 된다.


참고로, 외부조인(left join, right join)은 불가능하다. 다음에 설명할 조인on을 사용하면 외부조인이 가능해진다.

 

9. 조인 - on절

보통 on절은 다음 2가지 기능으로 사용한다.

1. 조인 대상 필터링
2. 연관관계 없는 엔티티 외부 조인

 

9-1) 조인 대상 필터링

/**
 * 회원과 팀을 조인하되, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
 * JPQL : select m, t from Member m join m.team t on t.name = 'teamA'
 */
@Test
public void join_on_filtering_test() {
    List<Tuple> tuples = queryFactory
            .select(member, team)
            .from(member)
            .leftJoin(member.team, team).on(team.name.eq("teamA"))
            .fetch();

    for (Tuple tuple : tuples) {
        System.out.println("tuple = " + tuple);
    }
}

실행되는 SQL은 다음과 같다.

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

쿼리를 보면 마지막에 and로 우리의 조건인 name 이 나오게 되었다.

and 앞의 id 비교는 leftJoin(member.team , ...) 덕분에 나오게 되는 부분이다.

 

실행 결과는 다음과 같다.

기대했던 결과대로 나오는것을 확인할 수 있다.

 

on 절을 활용해 조인 대상을(여기서는 team) 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면, where 절에서 필터링 하는 것과 기능이 동일하다.

.join(member.team, team)
.on(team.name.eq("teamA"))

위 아래가 동일한 결과를

.join(member.team, team)
.where(team.name.eq("teamA"))

따라서 on 절을 활용한 조인 대상 필터링을 사용할 때, inner join 이면 익숙한 where 절로 해결하고, outer join인 경우에만 on절을 활용하자.

 

9-2) 연관관계가 없는 Entity 간의 Outer Join

하이버네이트 5.1부터 on 을 사용해서 서로 연관관계가 없는 필드로 외부 조인하는 기능이 추가되었다. 물론 내부 조인도 가능하다.

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

잘보면 기존의 join과 다른점이 있다. 바로 leftJoin()의 인자로 하나만 전달하고 있다는 점 이다.

  • 일반조인: from(member).leftJoin(member.team, team)
  • on조인: from(member).leftJoin(team).on(xxx)

실행 결과는 다음과 같다.

 

10. Fetch Join

우선 fetch join을 사용하지 않는 코드부터 살펴보자.

@PersistenceUnit
EntityManagerFactory emf;

@Test
public void no_fetch_join() {
    em.flush();
    em.clear();

    Member findMember = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member1"))
            .fetchOne();

    // loading 된 Entity인지 확인하기
    boolean isLoaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
    assertThat(isLoaded).isFalse();
}

LAZY 로딩으로 걸려있는 Team같은 경우 아직 초기화가 되지 않았다.

실행되는 SQL도 딱 member부분만 select 해오고 있다.

따라서 assert문에서 false가 나왔다.

 

이번에는 Fetch Join을 사용하는 코드를 살펴보자.

@Test
public void  fetch_join() {
    em.flush();
    em.clear();

    Member findMember = queryFactory
            .selectFrom(member)
            .join(member.team, team).fetchJoin() // Fetch Join 사용!!
            .where(member.username.eq("member1"))
            .fetchOne();

    // loading 된 Entity인지 확인하기
    boolean isLoaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
    assertThat(isLoaded).isTrue();
}

join() 뒤에 fetchJoin()만 명시해주면 된다.

이번에는 team에 대한 정보까지 한번에 조회해오는 것을 확인할수 있다.

'BackEnd > JPA' 카테고리의 다른 글

[JPA] QueryDSL 중급문법 - 1  (0) 2022.05.14
[JPA] QueryDSL 기본문법 - 4  (0) 2022.05.14
[JPA] QueryDSL 기본문법 - 2  (0) 2022.05.13
[JPA] QueryDSL 기본문법 - 1  (0) 2022.05.13
[JPA] 나머지 기능들  (0) 2022.05.07

댓글