개요

연관관계가 있는 엔티티를 조인 - 기본 조인

/**
 * **INNER JOIN**
 * 예) 팀 A 에 소속된 모든 회원 조회
 */
@Test
void join() {
    //when
    List<Member> result = jpaQueryFactory
            .selectFrom(member)
            .join(member.team, team) // join(조인 대상, 별칭으로 사용할 Q타입 지정)
            .where(team.name.eq("teamA"))
            .fetch();

    //then
    assertThat(result)
            .extracting("userName")
            .containsExactly("member1", "member2");
		
		// Console Log
    // Member(id=3, userName=member1, age=10)
    // Member(id=4, userName=member2, age=20)
}

/**
 * **LEFT OUTER JOIN**
 * 예) 회원과 팀을 조인하면서, 팀 이름이 teamA 인 팀만 조인, 회원은 모두 조회
 * JPQL: SELECT m, t FROM Member m LEFT JOIN m.team t on t.name = 'teamA'
 * SQL : SELECT m.*, t.* FROM Member m LEFT JOIN Team t ON m.TEAM_ID = t.id and t.name = 'teamA'
 */
@Test
void join_on_filtering() {
    //given

    //when
    List<Tuple> result = jpaQueryFactory
            .select(member, team)
            .from(member)
            .leftJoin(member.team, team).on(team.name.eq("teamA"))
            .fetch();

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

    // Console Log
    // 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), null]
    // tuple = [Member(id=6, userName=member4, age=40), null]
}

@PersistenceUnit
EntityManagerFactory emf;

@Test
void fetchJoinUse() {
    //given
    em.flush();
    em.clear();

    //when
    Member findMember = jpaQueryFactory
            .selectFrom(member)
            .join(member.team, team).fetchJoin()
            .where(member.userName.eq("member1"))
            .fetchOne();

    //then
    boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
    assertThat(loaded).as("페치 조인 적용").isTrue();
}

연관관계가 없는 엔티티를 조인

/**
 * **THETA JOIN (연관관계가 없는 필드로 조인)**
 * 예) 회원의 이름이 팀 이름과 같은 회원 조회
 * 모든 멤버과 모든 팀을 가져와 조인하고 조건(where)에 맞게 조회 (DB 가 성능 최적화)
 * from 절에 여러 엔티티를 선택해서 세타 조인
 * 외부 조인 불가능 -> on 절을 사용하면 외부 조인 가능
 */
@Test
void theta_join() {
    //given
    em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));
    em.persist(new Member("teamC"));

    //when
    List<Member> result = jpaQueryFactory
            .select(member)
            .from(member, team)
            .where(member.userName.eq(team.name))
            .fetch();

    //then
    assertThat(result)
            .extracting("userName")
            .containsExactly("teamA", "teamB");
}

/**
 * **LEFT OUTER JOIN (연관관계 없는 엔티티 외부 조인)**
 * 예) 회원의 이름과 팀의 이름이 같은 대상 외부 조인
 * 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
void join_on_no_relation() {
    //given
    em.persist(new Member("teamA"));
    em.persist(new Member("teamB"));
    em.persist(new Member("teamC"));

    //when
    List<Tuple> result = jpaQueryFactory
            .select(member, team)
            .from(member)
            .leftJoin(team).on(member.userName.eq(team.name))
            .fetch();

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

    // Console Log
    // tuple = [Member(id=3, userName=member1, age=10), null]
    // tuple = [Member(id=4, userName=member2, age=20), null]
    // tuple = [Member(id=5, userName=member3, age=30), null]
    // tuple = [Member(id=6, userName=member4, age=40), null]
    // tuple = [Member(id=7, userName=teamA, age=0), Team(id=1, name=teamA)]
    // tuple = [Member(id=8, userName=teamB, age=0), Team(id=2, name=teamB)]
    // tuple = [Member(id=9, userName=teamC, age=0), null]
}