개발/JPA

[Querydsl] 검색 쿼리를 Querydsl로 구현하기(2)

highright96 2021. 7. 14.

조인

기본 조인

조인은 join(내부 조인), innerJoin(내부 조인), leftJoin(left 외부 조인), rightJoin(right 외부 조인)을 사용할 수 있다.

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

다음은 가장 기본적인 조인 방법이다.

List<Member> result = queryFactory
        .selectFrom(member)
        .leftJoin(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();

 

 from 절에서 여러 엔티티를 선택한다.

외부 조인이 불가능하지만, 조인 on을 사용하면 외부 조인이 가능하다.

 

조인 - on절

on절은 다음과 같은 기능을 한다.

1) 조인 대상 필터링

2) 연관관계 없는 엔티티 외부 조인(세타 조인)

 

 

조인 대상 필터링

쿼리 조건은 다음과 같다.

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

 

//JQPL
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'

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

 

만약 on절과 내부 조인을 사용할 경우 where 절에서 필터링 하는 것과 기능이 똑같다. 따라서 외부 조인일 때에만 on절을 활용하는 것이 옳바른 방법이다.

 

 

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

//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

//Querydsl
List<Tuple> result1 = queryFactory
        .select(member, team)
        .from(member)
        .leftJoin(team).on(member.username.eq(team.name))
        .fetch();

 

조인 - 페치 조인

페치 조인은 SQL에서 제공하는 기능은 아니다.

SQL 조인을 활용해 지연로딩으로 설정된 엔티티를 한 번에 조회하는 기능이다. 주로 성능 최적화에 사용하는 방법이다.

 

 

Member 엔티티

public class Member {
    @Id
    @GeneratedValue
    @Column(name = "member_id")
    private Long id;
    private String username;
    private int age;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}

 

Member 엔티티의 team이 지연 로딩으로 설정된 것을 볼 수 있다.

따라서 페치 조인을 적용하지 않고 일반 조인으로 Member를 조회하면 Team은 조회되지 않는다.

 

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

boolean loaded = emf.getPersistenceUnitUtil().isLoaded(findMember.getTeam());
assertThat(loaded).as("페치 조인 미적용").isFalse();

 

그러나 페치 조인을 사용하면 지연 로딩으로 설정된 Team도 조회되는 것을 확인할 수 있다.

 

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

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

 

JPA의 성능을 최적화하기 위해 대부분의 연관관계는 지연 로딩(LAZY)으로 설정하는 것이 좋다. 만약 즉시 로딩(EAGER)로 설정하면 해당 엔티티를 조인하는 다른 쿼리에서 예상하지 못한 조인이 발생할 수 있기 때문이다.

결론은 대부분의 연관관계는 지연 로딩(LAZY)으로 설정하고, 필요한 경우 페치 조인을 사용해 조회하자!

JPA 관련 글을 작성할 때 더 자세히 다루도록 하겠다.

 

Case 문

select, where, order by에서 사용 가능하다.

 

 

단순한 조건

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

 

복잡한 조건

caseBuilder를 사용한다.

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();

 

orderBy에서 Case 문 함께 사용하기

쿼리 조건은 다음과 같다.

1) 0~30살이 아닌 회원을 가장 먼저 출력

2) 0~20살 회원 출력

3) 21~30살 회원 출력

 

NumberExpression<Integer> rank = new CaseBuilder()
        .when(member.age.between(0, 20)).then(2)
        .when(member.age.between(21, 40)).then(3)
        .otherwise(1);

List<Tuple> result = queryFactory
        .select(member.username, rank)
        .from(member)
        .orderBy(rank.desc())
        .fetch();

 

Querydsl은 자바 코드로 작성하기 때문에 복잡한 조건을 변수로 선언해 어디서든 사용할 수 있다.

 

상수, 문자 더하기

상수 추가하기

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

 

문자 더하기 concat

List<String> result = queryFactory
        .select(member.username.concat("_").concat(member.age.stringValue()))
        .from(member)
        .where(member.username.eq("member1"))
        .fetch();

 

참고

김영한님의 실전! Querydsl

김영한님의 자바 ORM 표준 JPA 프로그래밍

댓글