📦 Database/QueryDSL

Query Builder - Inner & Outer & Cross Join

신건우 2023. 5. 20. 21:51

기본 Join 문법

조인의 기본 문법은 첫번째 파라미터에 조인 대상을 지정하고,

두번째 파라미터에 별칭(alias)으로 사용할 Q-Type을 지정하면 됩니다.

join(조인대상, 조인 대상 테이블(Q-Type Entity))

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

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

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

JPQL의 On과 성능 최적화를 위한 fetch join 제공


Inner Join

Hibernate 5.1 부터 On을 사용해 서로 연관관계가 없는 필드로 외부조인 하는 기능이 추가되었습니다.

물론 내부 조인도 가능합니다.


주의할 점은 leftJoin() 부분에는 일반 조인과 다르게 엔티티 하나만 들어갑니다.

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

내부 조인은 join() 메서드를 사용하여 테이블 간의 관계를 설정하는 방식입니다.

아래의 코드에서 order.memberorder 테이블과 member 테이블 사이의 관계를 나타냅니다.


join() 메서드의 첫 번째 인자로는 조인 대상 필드를, 두 번째 인자로는 조인할 대상 테이블을 전달합니다.

아래의 예제에서는 order.membermember를 내부 조인하여 연결합니다.

val query = JPAQueryFactory(entityManager) 
val order = QOrder.order 
val member = QMember.member 

val result = query 
        .select(order) 
        .from(order) 
        .join(order.member, member) 
        .fetch()

팀 A에 소속된 모든 회원을 조회하는 Inner Join 예시

/**  
 * 팀 A에 소속된 모든 회원 조인  
 * @desc Inner Join  
 */  
@Test  
fun join() {  
    val m = QMember.member  
    val t = QTeam.team  
    val result = queryFactory  
        .selectFrom(m)  
        .join(m.team, t)  
        .where(t.name.eq("teamA"))  
        .fetch()  

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

회원과 팀을 조인하면서, 팀 이름이 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'
/**  
 * 회원과 팀을 조인하면서, 팀 이름이 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' * @desc ON 절을 활용한 조인 (조인 대상 필터링)  
 */
 @Test  
fun filterJoin() {  
    val m = QMember.member  
    val t = QTeam.team  

    val result = queryFactory  
        .select(m, t)  
        .from(m)  
        .leftJoin(m.team, t).on(t.name.eq("teamA"))  
        .fetch()  

    result.forEach { println("tuple = $it") }

결과값

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]

Outer Join

외부 조인은 내부 조인과 유사하지만, join() 메서드 대신 leftJoin()이나 rightJoin() 메서드를 사용하여 조인을 수행합니다.

외부 조인은 조인 대상이 되는 테이블의 데이터와 일치하지 않아도 결과에 포함됩니다.

leftJoin() 메서드는 왼쪽 테이블을 기준으로 조인을 수행하고,

rightJoin() 메서드는 오른쪽 테이블을 기준으로 조인을 수행합니다.

val result = query 
        .select(order) 
        .from(order) 
        .leftJoin(order.member, member) 
        .fetch()

Cross Join

크로스 조인은 각 테이블의 모든 조합을 생성하는 방식으로, 특정 조인 조건 없이 단순히 테이블 간의 모든 데이터 조합을 가져옵니다.

from() 메서드를 중복하여 사용하여 크로스 조인을 수행할 수 있습니다.

  • 연관관계가 없는 필드로 조인
  • from 절에 여러 엔티티를 선택해서 크로스 조인
  • 외부 조인 불가능 -> ON 을 사용하여 외부 조인 해결
/**  
 * 크로스 조인 (연관관계가 없는 필드로 조인)  
 * 회원의 이름이 팀 이름과 같은 회원 조회  
 * @desc Cross Join 
 */  
@Test  
fun thetaJoin() {  
    val m = QMember.member  
    val t = QTeam.team  

    val result = queryFactory  
        .select(m)  
        .from(m, t)  
        .where(m.name.eq(t.name))  
        .fetch()  

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