상세 컨텐츠

본문 제목

[SPRING] Querydsl | 문법(2) 조인연산

JAVA/기본 & 강의복습

by ranlan 2021. 4. 30. 12:24

본문

728x90

기본 조인

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

join(innerJoin), leftJoin, rightJoin

예) teamA에 소속된 모든 회원 조회

List<Member> result = queryFactory
        .selectFrom(member)
        .join(member.team, team)
        .where(team.name.eq("teamA"))
        .fetch();

 

세타 조인

연관관계가 없는 필드와 조인

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

예) team이름과 member이름이 같은 회원 조회(from절에 조인할 엔티티 나열, *외부 조인 불가능)

 

조인 ON절

on절을 활용한 조인(JPA2.1부터 지원)

 

1) 조인 대상 필터링

예) 회원과 팀 조인하되 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'

 

leftJoin과 on절

public 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=30), null]
tuple: [Member(id=6, username=member4, age=40), null]

teamA가 아닌 member의 경우 team이 null * 외부조인이 꼭 필요한 경우만 사용 권장

 

innerJoin과 on절

public void join_on_filtering() {
    List<Tuple> result = queryFactory
            .select(member, team)
            .from(member)
            .join(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)]

teamA가 아닌 member가 제외됨

 

innerJoin과 where절

public void join_on_filtering() {
    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);
    }
}

실행 결과

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)]

위의 결과와 동일

 

* 내부조인의 경우 where절로 해결 권장

 

2) 연관관계가 없는 외부조인

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

예) 회원과 팀 이름이 같은 경우 조회

public void join_on_no_relation() {
    List<Tuple> result = queryFactory
            .select(member, team)
            .from(member)
            //.leftJoin(member.team, team) 일반적인 경우
            .leftJoin(team) // 연관관계가 없을 때
            .on(member.username.eq(team.name))
            .fetch();

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

join절에 엔티티가 하나만 들어감

on 조건에 맞는(team 이름과 member의 이름이 같은 경우)만 join하여 가져옴

 

실행 결과

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]

조건에 맞지 않는 경우 team은 null

 

페치조인

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

Member member = queryFactory
	.selectFrom(member)
        .join(member.team, team).fetchJoin()
	.where(member.username.eq("member1"))
	.fetchOne();

조인절 뒤에 fetchJoin()

 

서브쿼리

밖의 쿼리와 서브 쿼리의 테이블 별칭의 별칭은 다르게 두어야함으로 Qtype별칭을 따로 지정해주어야함

QMember memberSub = new QMember("memberSub");

예) 나이가 평균 이상인 회원 조회

JPAExpressions

List<Member> result = queryFactory
    .selectFrom(member)
    .where(member.age.goe(
            JPAExpressions
                    .select(memberSub.age.avg())
                    .from(memberSub)
    ))
    .fetch();

static import 활용

import static com.querydsl.jpa.JPAExpressions.select;

List<Member> result = queryFactory
    .selectFrom(member)
    .where(member.age.goe(
            select(memberSub.age.avg())
                .from(memberSub)
    ))
    .fetch();

IN절

List<Member> result = queryFactory
        .selectFrom(member)
        .where(member.age.in(
                select(memberSub.age)
                        .from(memberSub)
                        .where(memberSub.age.goe(10))
        ))
        .fetch();

select절에 서브쿼리

List<Tuple> list = queryFactory
        .select(member.username,
                JPAExpressions
               .select(memberSub.age.avg())
                .from(memberSub))
        .from(member)
        .fetch();

// static import
import static com.querydsl.jpa.JPAExpressions.*;

List<Tuple> list = queryFactory
        .select(member.username,
                select(memberSub.age.avg())
                        .from(memberSub))
        .from(member)
        .fetch();

 

화면에 맞춘 쿼리는 좋지 않다!  쿼리 재사용성이 떨어지고 쿼리가 복잡해짐

from 절의 서브쿼리 불가 - JPA JPQL는 from 절의 서브쿼리(인라인 뷰)는 지원하지 않는다. (Querydsl도 마찬가지)

 1) 서브쿼리를 join으로 변경 (불가능한 경우도 있음)

 2) 애플리케이션에서 쿼리 분리하여 2번 실행

 3) nativeSQL 사용

 

CASE문

select, 조건절(where), order by에서 사용 가능

// 단순한 경우
List<String> result = queryFactory
        .select(member.age
                .when(10).then("열살")
                .when(20).then("스무살")
                .otherwise("기타"))
        .from(member)
        .fetch();

// 복잡한 경우
List<String> result = queryFactory
        .select(new CaseBuilder()
                .when(member.age.between(0, 20)).then("0~20살")
                .when(member.age.between(21, 30)).then("21~30살")
                .otherwise("기타"))
        .from(member)
        .fetch();

마찬가지로 case문에서 구간별로 별칭을 다닌 등의 로직은 쿼리가 아닌 애플리케이션단에서 하는 것이 좋음

 

상수, 문자 더하기

상수

List<Tuple> result = queryFactory
        .select(member.username, Expressions.constant("A"))
        .from(member)
        .fetch();

Expressions.constant() 사용

 

문자열

// {username}_{age}
List<String> result = queryFactory
        .select(member.username.concat("_").concat(member.age.stringValue())) //int -> string
        .from(member)
        .where(member.username.eq("member!"))
        .fetch();

enum 처리 시 자주 사용

 

728x90

관련글 더보기

댓글 영역