[Querydsl] 12. 기본 조인, 세타 조인, on 절 활용(조인 대상 필터링, 연관관계 없는 필드로 외부 조인)

민정·2023년 1월 9일

QueryDSL

목록 보기
12/18
post-thumbnail

✨ 기본 조인

✅ 문법

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

✅ 예제 코드

팀 A에 소속된 모든 회원을 조회

팀과 회원을 left join한 뒤, where절로 team.name = "teamA"

/**
     * 조인
     *
     * 팀 A에 소속된 모든 회원을 조회
     */
    @Test
    public void join() {
        List<Member> result = queryFactory
                .selectFrom(member)
                .join(member.team, team) // 2번째 파라미터는 QTeam.team을 의미
                .where(team.name.eq("teamA"))
                .fetch();

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

✅ 핵심 코드

.selectFrom(member).join(member.team, team)

✅ 실행된 JPQL

select
        member1 
    from
        Member member1   
    inner join
        member1.team as team 
    where
        team.name = ?1 

✅ 문법

  • 내부 조인 : join(), innerJoin()
  • left 외부 조인 : leftJoin()
  • right 외부 조인 : rightJoin()

✨ 세타 조인

연관관계가 필요 없는 필드로 조인

.join을 사용하는 게 아니라, .from에 여러 엔티티를 선택해서 세타 조인

단점 : 외부 조인(left join, right join) 불가능 -> 조인 on절 사용시 외부 조인 가능

✅ 예시 코드

회원의 이름이 팀 이름과 같은 회원 조회

회원의 이름팀 이름은 관계가 없는 필드

    /**
     * 세타 조인 - 연관관계 없는 필드 조인
     * 회원의 이름이 팀 이름과 같은 회원 조회
     */
    @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) // 그냥 from절에 Q타입 나열 -> 모든 member 테이블 row, 모든 team 테이블의 row 조인
                .where(member.username.eq(team.name))
                .fetch();

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

✅ 실행된 JPQL

	select
        member1 
    from
        Member member1,
        Team team 
    where
        member1.username = team.name 


✨ on 절

on절 활용한 조인은 JPA 2.1부터 지원한다고 한다.

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

1. 조인 대상 필터링

✅ 예제 코드

회원과 팀 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회

JPQL : SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'teamA';

 /**
     * ON절 - 1. 조인 대상 필터링
     *
     * 회원과 팀 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모두 조회
     * JPQL: SELECT m, t FROM Member m LEFT JOIN m.team t ON t.name = 'teamA';
     */
    @Test
    public void join_on_filtering() {
        List<Tuple> result = queryFactory
                .select(member, team) // 결과 tuple인 이유: select에서 여러 타입 지정되어서
                .from(member)
                .leftJoin(member.team, team).on(team.name.eq("teamA"))
                .fetch();

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

✅ 결과

member left join team이니까 member는 전체 다 조회된다.
대신, on 절로 team테이블에서 teamA인 row만 조인 대상에 포함되도록 제한
member 중에서 teamA가 아닌 것들은 team = null로 표시

✅ 실행된 JPQL, SQL


참고) 내부조인(inner join, join)의 on절 = where과 같다.

on절을 활용해서 조인 대상을 필터링할 때, 외부 조인이 아닌 내부 조인을 사용할 경우,where절에서 필터링하는 것과 기능이 동일하다.

따라서 내부조인이면 on절 대신 익숙한 where절을 사용해서 조인 대상을 필터링하고, 외부 조인인 경우에만 on 절을 사용하자.

// 내부 조인에서의 on절은 where절에 작성해도 결과가 같다.
        List<Tuple> innerJoin_where = queryFactory
                .select(member, team)
                .from(member)
                .join(member.team, team)
                .where(team.name.eq("teamA"))
                .fetch();

        for (Tuple tuple : innerJoin_where) {
            System.out.println("tuple = " + tuple); // 대신 결과에서는 모든 회원이 아니라, teamA인 회원만 나옴(2명)
        }

✅ 결과

on절로 Team 테이블의 row 중 teamA인 row로 제한해도, 내부 조인이라서 양 측 테이블에 둘 다 값이 존재해야 조회된다.


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

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

물론 내부 조인도 가능하다.
.join(team).on(member.username.eq(team.name))도 가능

leftJoin() 부분 문법을 잘 봐야함!

  • 일반 조인 : leftJoin(member.team, team) -> member.team_id = team.team_id(fk - pk 조인)
  • on 조인 : from(member).leftJoin(team).on(xxx)

✅ 예제 코드

회원의 이름팀의 이름이 같은 대상 외부 조인

/**
     *  ON절 - 2. 연관관계 없는 엔티티 외부 조인
     *
     * 예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
     *
     * JPQL: SELECT m, t FROM Member m LEFT JOIN Team t on m.username = t.name
     * SQL: SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.username = t.name
     */
    @Test
    public void join_on_no_relation() {
        em.persist(new Member("teamA"));
        em.persist(new Member("teamB"));

        // on 조인
        List<Tuple> result = queryFactory
                .select(member, team)
                .from(member)
                .leftJoin(team).on(member.username.eq(team.name)) // member.username과 team.name은 관계가 없는데, 이 두 필드로 외부 조인
                .fetch();

        // 일반 leftjoin
        List<Tuple> result2 = queryFactory
                .select(member, team)
                .from(member)
                .leftJoin(member.team, team) // member.team_id = team.team_id
                .fetch();

        // leftJoin 부분 잘 보기
        // 일반 조인 : leftJoin(member.team, team)
        // on 조인 : from(member).leftJoin(team).on(xxx)


        System.out.println("======= <ON 조인> : .from(member).leftJoin(team).on(member.username.eq(team.name)) =======");
        for (Tuple tuple : result) {
            System.out.println("tuple = " + tuple);
        }
        System.out.println("\n\n");

        System.out.println("======= <일반 조인> : .from(member).leftjoin(member.team, team) =======");
        for (Tuple tuple : result2) {
            System.out.println("tuple = " + tuple);
        }

    }

✅ 결과 (on 조인, 일반 조인 비교)

1. on 조인


-> on 부분 fk = 비교 없다!

나의 생각)
각 왼쪽 테이블 row마다 join할 오른쪽 테이블의 row를 on 조건 으로 거르고 조인을 한다고 생각하면 되는 거 같다.


2. 일반 조인



출처

김영한 강사님 - 인프런 실전! Querydsl

0개의 댓글