스프링

 

중급자를 위해 준비한
[백엔드, 웹 개발] 강의입니다.

Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!

✍️
이런 걸
배워요!

Querydsl을 기초부터 실무활용까지 한번에 배울 수 있습니다.

단순한 기능 설명을 넘어 실무활용 노하우를 배울 수 있습니다.

JPA를 사용할 때 동적 쿼리와 복잡한 쿼리 문제를 해결할 수 있습니다.

복잡한 쿼리, 동적 쿼리는 이제 안녕! 
Querydsl로 자바 백엔드 기술을 단단하게.

???? 본 강의는 로드맵 과정입니다.

  • 본 강의는 자바 백엔드 개발의 실전 코스를 완성하는 마지막 강의입니다. 스프링 부트와 JPA 실무 완전 정복 로드맵을 우선 확인해주세요. (링크)

 

 

강좌 

https://www.inflearn.com/course/querydsl-실전#

 

 

강의자료

https://github.com/braverokmc79/jpa-basic-lecture-file2

 

 

 

소스 :

https://github.com/braverokmc79/jpa-querydsl

 

 

 

 

 

 

 

 

 

 

[4] 기본 문법

 

 

9.시작 - JPQL vs Querydsl

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30122&tab=curriculum

 

 

 

테스트 기본 코드

package study.querydsl;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import study.querydsl.entity.Member;
import study.querydsl.entity.QMember;
import study.querydsl.entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
import static study.querydsl.entity.QMember.*;
@SpringBootTest
@Transactional
public class QuerydslBasicTest {
    @PersistenceContext
    EntityManager em;
    @BeforeEach
    public void before() {
        Team teamA = new Team("teamA");
        Team teamB = new Team("teamB");
        em.persist(teamA);
        em.persist(teamB);
        Member member1 = new Member("member1", 10, teamA);
        Member member2 = new Member("member2", 20, teamA);
        Member member3 = new Member("member3", 30, teamB);
        Member member4 = new Member("member4", 40, teamB);
        em.persist(member1);
        em.persist(member2);
        em.persist(member3);
        em.persist(member4);
    }
}

 

지금부터는 이 예제로 실행

 

 

Querydsl vs JPQL

Querydsl vs JPQL
@Test
public void startJPQL() {
    //member1을 찾아라.
    String qlString =
        "select m from Member m " +
        "where m.username = :username";
    Member findMember = em.createQuery(qlString, Member.class)
        .setParameter("username", "member1")
        .getSingleResult();
    assertThat(findMember.getUsername()).isEqualTo("member1");
}
@Test
public void startQuerydsl() {
    //member1을 찾아라.
    JPAQueryFactory queryFactory = new JPAQueryFactory(em);
    QMember m = new QMember("m");
    Member findMember = queryFactory
        .select(m)
        .from(m)
        .where(m.username.eq("member1")) //파라미터 바인딩 처리
        .fetchOne();
    assertThat(findMember.getUsername()).isEqualTo("member1");
}

 

 

EntityManager 로 JPAQueryFactory 생성


Querydsl은 JPQL 빌더


JPQL: 문자(실행 시점 오류), Querydsl: 코드(컴파일 시점 오류)


JPQL: 파라미터 바인딩 직접, Querydsl: 파라미터 바인딩 자동 처리

 

 

 

JPAQueryFactory를 필드로

package study.querydsl;
import com.querydsl.jpa.impl.JPAQueryFactory;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;
import study.querydsl.entity.Member;
import study.querydsl.entity.QMember;
import study.querydsl.entity.Team;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
import static study.querydsl.entity.QMember.*;
@SpringBootTest
@Transactional
public class QuerydslBasicTest {
    @PersistenceContext
    EntityManager em;
    JPAQueryFactory queryFactory;
    @BeforeEach
    public void before() {
        queryFactory = new JPAQueryFactory(em);
        //…
    }
    @Test
    public void startQuerydsl2() {
        //member1을 찾아라.
        QMember m = new QMember("m");
        Member findMember = queryFactory
            .select(m)
            .from(m)
            .where(m.username.eq("member1"))
            .fetchOne();
        assertThat(findMember.getUsername()).isEqualTo("member1");
    }
}

 

 

 

JPAQueryFactory를 필드로 제공하면 동시성 문제는 어떻게 될까? 동시성 문제는 JPAQueryFactory를

생성할 때 제공하는 EntityManager(em)에 달려있다. 스프링 프레임워크는 여러 쓰레드에서 동시에 같은

EntityManager에 접근해도, 트랜잭션 마다 별도의 영속성 컨텍스트를 제공하기 때문에, 동시성 문제는

걱정하지 않아도 된다.

 

 

 

 

 

 

 

 

10.기본 Q-Type 활용

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30123&tab=curriculum

 

Q클래스 인스턴스를 사용하는 2가지 방법

 

 

QMember qMember = new QMember("m"); //별칭 직접 지정
QMember qMember = QMember.member; //기본 인스턴스 사용

 

 

기본 인스턴스를 static import와 함께 사용

 

import static study.querydsl.entity.QMember.*;
@Test
public void startQuerydsl3() {
    //member1을 찾아라.
    Member findMember = queryFactory
        .select(member)
        .from(member)
        .where(member.username.eq("member1"))
        .fetchOne();
    assertThat(findMember.getUsername()).isEqualTo("member1");
}

 

 

다음 설정을 추가하면 실행되는 JPQL을 볼 수 있다.

 

spring.jpa.properties.hibernate.use_sql_comments: true

 

 

참고: 같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용하자

 

 

 

 

 

 

 

 

 

 

11.검색 조건 쿼리

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30124&tab=curriculum

 

 

기본 검색 쿼리

@Test
public void search() {
    Member findMember = queryFactory
        .selectFrom(member)
        .where(member.username.eq("member1")
            .and(member.age.eq(10)))
        .fetchOne();
    assertThat(findMember.getUsername()).isEqualTo("member1");
}

 

 

검색 조건은 .and() , . or() 를 메서드 체인으로 연결할 수 있다

 

참고: select , from 을 selectFrom 으로 합칠 수 있음

 

 

 

JPQL이 제공하는 모든 검색 조건 제공

 

member.username.eq("member1") // username = 'member1'
member.username.ne("member1") //username != 'member1'
member.username.eq("member1").not() // username != 'member1'
member.username.isNotNull() //이름이 is not null
member.age.in(10, 20) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10, 30) //between 10, 30
member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30
member.username.like("member%") //like 검색
member.username.contains("member") // like ‘%member%’ 검색
member.username.startsWith("member") //like ‘member%’ 검색
    ...

 

 

 

AND 조건을 파라미터로 처리

@Test
public void searchAndParam() {
    List < Member > result1 = queryFactory
        .selectFrom(member)
        .where(member.username.eq("member1"),
            member.age.eq(10))
        .fetch();
    assertThat(result1.size()).isEqualTo(1);
}

 

where() 에 파라미터로 검색조건을 추가하면 AND 조건이 추가됨
이 경우 null 값은 무시 메서드 추출을 활용해서 동적 쿼리를 깔끔하게 만들 수 있음 뒤에서 설명

 

 

 

 

 

 

 

 

12.결과 조회

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30125&tab=curriculum

 

fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환


fetchOne() : 단 건 조회

결과가 없으면 : null  결과가 둘 이상이면 : com.querydsl.core.NonUniqueResultException


fetchFirst() : limit(1).fetchOne()


fetchResults() : 페이징 정보 포함, total count 쿼리 추가 실행


fetchCount() : count 쿼리로 변경해서 count 수 조회
 

 

 

 

    @Test
    public void resultFetch(){
        //List
        List<Member> fetch = queryFactory
                                .selectFrom(member)
                                .fetch();


        //단건
        Member findMember1 = queryFactory
                                .selectFrom(member)
                                .fetchOne();


        //처음 한 건 조회
        Member findMember2=queryFactory
                            .selectFrom(member)
                            .fetchFirst();


        //페이징에서 사용
        QueryResults<Member> results =queryFactory
                    .selectFrom(member).fetchResults();

            results.getTotal();
            List<Member> content=results.getResults();


            //count 쿼리로 사용
        long count=queryFactory
                .selectFrom(member)
                .fetchCount();

    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

13.정렬

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30126&tab=curriculum

 

 

   /**
     * 회원 정렬 순서
     * 1. 회원 나이 내림차순(desc)
     * 2. 회원 이름 올림차순(asc)
     * 단 2에서 회원 이름이 없으면 마지막에 출력(nulls last)
     */
    @Test
    public  void sort(){
        em.persist(new Member(null, 100));
        em.persist(new Member("member5", 100));
        em.persist(new Member("member6", 100));

        List<Member> result = queryFactory.selectFrom(member)
                .where(member.age.eq(100))
                .orderBy(member.age.desc(), member.username.asc().nullsLast())
                .fetch();


        Member member5 = result.get(0);
        Member member6 = result.get(1);
        Member memberNull = result.get(2);
        Assertions.assertThat(member5.getUsername()).isEqualTo("member5");
        Assertions.assertThat(member6.getUsername()).isEqualTo("member6");
        Assertions.assertThat(memberNull.getUsername()).isNull();
    }

 

 

desc() , asc() : 일반 정렬


nullsLast() , nullsFirst() : null 데이터 순서 부여

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

14.페이징

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30127&tab=curriculum

 

 

다음 참조 :

실전! 스프링 데이터 - JPA -[5] 확장 기능 ★ (사용자 정의 리포지토리 구현 , 확장 - 도메인 클래스 컨버터, 확장 - 페이징과 정렬)

https://macaronics.net/index.php/m01/spring/view/2081

 

 

조회 건수 제한

 

@Test
public void paging1() {
    List <Member> result = queryFactory
        .selectFrom(member)
        .orderBy(member.username.desc())
        .offset(1) //0부터 시작(zero index)
        .limit(2) //최대 2건 조회
        .fetch();
    assertThat(result.size()).isEqualTo(2);
}

 

 

 

전체 조회 수가 필요하면?

 

@Test
public void paging2() {
    QueryResults <Member> queryResults = queryFactory
        .selectFrom(member)
        .orderBy(member.username.desc())
        .offset(1)
        .limit(2)
        .fetchResults();
    assertThat(queryResults.getTotal()).isEqualTo(4);
    assertThat(queryResults.getLimit()).isEqualTo(2);
    assertThat(queryResults.getOffset()).isEqualTo(1);
    assertThat(queryResults.getResults().size()).isEqualTo(2);
}

 

 

> 주의: count 쿼리가 실행되니 성능상 주의!

 


> 참고: 실무에서 페이징 쿼리를 작성할 때, 데이터를 조회하는 쿼리는 여러 테이블을 조인해야 하지만,


count 쿼리는 조인이 필요 없는 경우도 있다. 그런데 이렇게 자동화된 count 쿼리는 원본 쿼리와 같이 모두


조인을 해버리기 때문에 성능이 안나올 수 있다. count 쿼리에 조인이 필요없는 성능 최적화가 필요하다면,    count 전용 쿼리를 별도로 작성해야 한다.

 

 

 

 

 

 

 

 

 

 

 

 

15.집합

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30128&tab=curriculum

 

   /**
     * JPQL
     * select
     * COUNT(m), //회원수
     * SUM(m.age), //나이 합
     * AVG(m.age), //평균 나이
     * MAX(m.age), //최대 나이
     * MIN(m.age) //최소 나이
     * from Member m
     */
    @Test
    public void aggregation(){
        List<Tuple> result = queryFactory.select(
                        member.count(),
                        member.age.sum(),
                        member.age.avg(),
                        member.age.max(),
                        member.age.min()
                )
                .from(member).fetch();

        Tuple tuple =result.get(0);
        Assertions.assertThat(tuple.get(member.count())).isEqualTo(4);
        Assertions.assertThat(tuple.get(member.age.sum())).isEqualTo(100);
        Assertions.assertThat(tuple.get(member.age.avg())).isEqualTo(25);
        Assertions.assertThat(tuple.get(member.age.max())).isEqualTo(40);
        Assertions.assertThat(tuple.get(member.age.min())).isEqualTo(10);

    }

 

 

JPQL이 제공하는 모든 집합 함수를 제공한다

.
tuple은 프로젝션과 결과반환에서 설명한다.

 

 

GroupBy 사용

 

    /**
     * 팀의 이름과 각 팀의 평균 연령을 구해라
     * @throws Exception
     */
    @Test
    public void group() throws Exception{

        List<Tuple> result = queryFactory.select(team.name, member.age.avg())
                .from(member).join(member.team, team)
                .groupBy(team.name).fetch();

        Tuple teamA=result.get(0);
        Tuple teamB=result.get(1);

       Assertions.assertThat(teamA.get(team.name)).isEqualTo("teamA");
       Assertions.assertThat(teamA.get(member.age.avg())).isEqualTo(15); //(10+20)/2

        Assertions.assertThat(teamA.get(team.name)).isEqualTo("teamB");
        Assertions.assertThat(teamA.get(member.age.avg())).isEqualTo(35); //(30+40)/2

    }

 

 

 

groupBy , 그룹화된 결과를 제한하려면 having


groupBy(), having() 예시

 

 …
.groupBy(item.price)
.having(item.price.gt(1000))
 …

 

 

 

 

 

 

 

 

 

16.조인 - 기본 조인

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30129&tab=curriculum

 

기본 조인


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

 

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

 

기본 조인

 /**
     * 팀 A 에 소속된 모든 회원
     */

    @Test
    public void join(){
        List<Member> result = queryFactory
                .selectFrom(member)
                .join (member.team,team)
                //.leftJoin(member.team, team)
                .where(team.name.eq("teamA"))
                .fetch();

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

 

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


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


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


JPQL의 on 과 성능 최적화를 위한 fetch 조인 제공 다음 on 절에서 설명

 

 

 

세타 조인


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

    /**
     * 연관 관계가 없는 것도 조인이 가능하다.
     * 세타 조인
     * 회원의 이름이 팀 이름과 같은 회원을 조회
     */

    @Test
    public void theta_join(){
        em.persist(new Member("teamA"));
        em.persist(new Member("teamB"));

        /** 기존과 다르게 from 절에서 두개 테이블을 묶어줬다.
         * 모든 팀테이블 조인 세타 조인  =>  member member0_ cross
         *
         selec member1  from
         Member member1,
                Team team
         where
            member1.username = team.name
         * **/
        List<Member> result = queryFactory.select(member)
                .from(member, team)
                .where(member.username.eq(team.name)).fetch();

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

 

from 절에 여러 엔티티를 선택해서 세타 조인


외부 조인 불가능 다음에 설명할 조인 on을 사용하면 외부 조인 가능

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

17.조인 - on절

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30130&tab=curriculum

 

ON절을 활용한 조인(JPA 2.1부터 지원)


1. 조인 대상 필터링


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

 

 

 

 

1. 조인 대상 필터링

 

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

  /**
     * 예) 회원과 팀을 조인하면서, 팀 이름이 teamA인 팀만 조인, 회원은 모드 조회
     * 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_filtering(){
        List<Tuple> result = queryFactory
                .select(member,team)
                .from(member)
                //.leftJoin(member.team, team)
                .join(member.team, team)
                //.on(team.name.eq("teamA"))
                //inner join  경우 같다 on 절을 쓰나 where 절을 쓰나 동일하다.
                .where(team.name.eq("teamA"))
                .fetch();

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

        /** innerjoin 경우 다음 방법 을 추천 **/
        queryFactory
                .select(member,team)
                .from(member)
                .join(member.team, team)
                .where(team.name.eq("teamA"))
                .fetch();
    }

 

결과

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]

 

 

 

참고: on 절을 활용해 조인 대상을 필터링 할 때, 외부조인이 아니라 내부조인(inner join)을 사용하면,


where 절에서 필터링 하는 것과 기능이 동일하다. 따라서 on 절을 활용한 조인 대상 필터링을 사용할 때,


내부조인 이면 익숙한 where 절로 해결하고, 정말 외부조인이 필요한 경우에만 이 기능을 사용하자.

 

 

 

 

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

 

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

 

    /**
     * 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"));
        em.persist(new Member("teamB"));


        //주의  .leftJoin(team) 엔티티 하나만 들어갔다.
        List<Tuple> result = queryFactory
                .select(member,team)
                .from(member)
                .leftJoin(team)
                .on(member.username.eq(team.name))
                .fetch();


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

 

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


주의! 문법을 잘 봐야 한다. leftJoin() 부분에 일반 조인과 다르게 엔티티 하나만 들어간다.


일반조인:   leftJoin(member.team, team)


on조인:  from(member).leftJoin(team).on(xxx)

 

 

 

출력 쿼리 결과

/* select
        member1,
        team 
    from
        Member member1   
    left join
        Team team with member1.username = team.name */ select
            member0_.member_id as member_i1_1_0_,
            team1_.team_id as team_id1_2_1_,
            member0_.age as age2_1_0_,
            member0_.team_id as team_id4_1_0_,
            member0_.username as username3_1_0_,
            team1_.name as name2_2_1_ 
        from
            member member0_ 
        left outer join
            team team1_ 
                on (
                    member0_.username=team1_.name
                )

 

 

 

결과

t=[Member(id=3, username=member1, age=10), null]
t=[Member(id=4, username=member2, age=20), null]
t=[Member(id=5, username=member3, age=30), null]
t=[Member(id=6, username=member4, age=40), null]
t=[Member(id=7, username=teamA, age=0), Team(id=1, name=teamA)]
t=[Member(id=8, username=teamB, age=0), Team(id=2, name=teamB)]

 

 

 

 

 

 

 

 

 

 

 

18.조인 - 페치 조인

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30131&tab=curriculum

 

 

페치 조인은 SQL에서 제공하는 기능은 아니다. SQL조인을 활용해서 연관된 엔티티를 SQL 한번에


조회하는 기능이다. 주로 성능 최적화에 사용하는 방법이다.

 

 

 

페치 조인 미적용

 

지연로딩으로 Member, Team SQL 쿼리 각각 실행

 

    @PersistenceUnit
    EntityManagerFactory emf;


    @Test
    public void fetchJoinNo(){
        em.flush();
        em.clear();

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

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

 

 

페치 조인 적용

즉시로딩으로 Member, Team SQL 쿼리 조인으로 한번에 조회

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

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

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

 

=>출력

 /* select
        member1 
    from
        Member member1   
    inner join
        fetch member1.team as team 
    where
        member1.username = ?1 */ select
            member0_.member_id as member_i1_1_0_,
            team1_.team_id as team_id1_2_1_,
            member0_.age as age2_1_0_,
            member0_.team_id as team_id4_1_0_,
            member0_.username as username3_1_0_,
            team1_.name as name2_2_1_ 
        from
            member member0_ 
        inner join
            team team1_ 
                on member0_.team_id=team1_.team_id 
        where
            member0_.username=?

 

사용방법

 

join(), leftJoin() 등 조인 기능 뒤에 fetchJoin() 이라고 추가하면 된다.

 

> 참고: 페치 조인에 대한 자세한 내용은 JPA 기본편이나, 활용2편을 참고하자

 

 

 

 

 

 

 

 

 

 

 

★19.서브 쿼리 ★

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30132&tab=curriculum

 

 

서브 쿼리 eq 사용

 

    /**
     * 나이가 가장 많은 회원 조회
     */

    @Test
    public void subQuery(){
        QMember memberSub =new QMember("memberSub");

        List<Member> reseult = queryFactory.
                selectFrom(member)
                .where(member.age.eq(
                                JPAExpressions.select(memberSub.age.max())
                                        .from(memberSub)
                        )
                ).fetch();


        Assertions.assertThat(reseult).extracting("age")
                .containsExactly(40);
    }

 

출력 쿼리 결과=>

    /* select
        member1 
    from
        Member member1 
    where
        member1.age = (
            select
                max(memberSub.age) 
            from
                Member memberSub
        ) */ select
            member0_.member_id as member_i1_1_,
            member0_.age as age2_1_,
            member0_.team_id as team_id4_1_,
            member0_.username as username3_1_ 
        from
            member member0_ 
        where
            member0_.age=(
                select
                    max(member1_.age) 
                from
                    member member1_
            )

 

 

 

 

 

서브 쿼리 goe 사용

    /**
     * 나이가 평균 이상인회원
     * lt <
     *
     * loe <=
     *
     * gt >
     *
     * goe >=
     */

    @Test
    public void subQueryGoe(){
        QMember memberSub =new QMember("memberSub");

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


        Assertions.assertThat(reseult).extracting("age")
                .containsExactly(30,40);
    }

 

출력 쿼리 결과=>

 /* select
        member1 
    from
        Member member1 
    where
        member1.age >= (
            select
                avg(memberSub.age) 
            from
                Member memberSub
        ) */ select
            member0_.member_id as member_i1_1_,
            member0_.age as age2_1_,
            member0_.team_id as team_id4_1_,
            member0_.username as username3_1_ 
        from
            member member0_ 
        where
            member0_.age>=(
                select
                    avg(cast(member1_.age as double)) 
                from
                    member member1_
            )

 

 

 

서브쿼리 여러 건 처리 in 사용

 

    @Test
    public void subQueryIn(){
        QMember memberSub =new QMember("memberSub");

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


        Assertions.assertThat(reseult).extracting("age")
                .containsExactly(20,30,40);
    }

 

출력 쿼리 결과=>

    /* select
        member1 
    from
        Member member1 
    where
        member1.age in (
            select
                memberSub.age 
            from
                Member memberSub 
            where
                memberSub.age > ?1
        ) */ select
            member0_.member_id as member_i1_1_,
            member0_.age as age2_1_,
            member0_.team_id as team_id4_1_,
            member0_.username as username3_1_ 
        from
            member member0_ 
        where
            member0_.age in (
                select
                    member1_.age 
                from
                    member member1_ 
                where
                    member1_.age>?
            )

 

 

 

 

select 절에 subquery

 

List <Tuple> fetch = queryFactory
    .select(member.username,
        JPAExpressions
        .select(memberSub.age.avg())
        .from(memberSub)
    ).from(member)
    .fetch();
for (Tuple tuple: fetch) {
    System.out.println("username = " + tuple.get(member.username));
    System.out.println("age = " +
        tuple.get(JPAExpressions.select(memberSub.age.avg())
            .from(memberSub)));
}

static import 활용

    /**
     * 셀렉트 절에서 서브쿼리 사용
     */
    @Test
    public void selectSubQuery(){
        QMember memberSub =new QMember("memberSub");
        
        List<Tuple> result = queryFactory
                .select(member.username,
                        select(memberSub.age.avg())
                        .from(memberSub))
                .from(member)
                .fetch();

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

출력 쿼리 결과=>

    /* select
        member1.username,
        (select
            avg(memberSub.age) 
        from
            Member memberSub) 
    from
        Member member1 */ select
            member0_.username as col_0_0_,
            (select
                avg(cast(member1_.age as double)) 
            from
                member member1_) as col_1_0_ 
        from
            member member0_

 

tuple = [member1, 25.0]
tuple = [member2, 25.0]
tuple = [member3, 25.0]
tuple = [member4, 25.0]

 

 

 

★from 절의 서브쿼리 한계

 

JPA JPQL 서브쿼리의 한계점으로 from 절의 서브쿼리(인라인 뷰)는 지원하지 않는다. 당연히 Querydsl
도 지원하지 않는다. 하이버네이트 구현체를 사용하면 select 절의 서브쿼리는 지원한다. Querydsl도
하이버네이트 구현체를 사용하면 select 절의 서브쿼리를 지원한다.

 

 

★ from 절의 서브쿼리 해결방안


1. 서브쿼리를 join으로 변경한다. (가능한 상황도 있고, 불가능한 상황도 있다.)


2. 애플리케이션에서 쿼리를 2번 분리해서 실행한다.


3. nativeSQL을 사용한다

 

 

 

 

 

 

 

 

 

 

 

 

20.Case 문

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30133&tab=curriculum

 

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

 

1) 단순한 조건

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

        for(String s :result){
            System.out.println("s = " + s);
        }
    }

 

쿼리결과

select
case when member1.age = ? 1 then ? 2 when member1.age = ? 3 then ? 4
else '기타'
end
from Member member1 * / select case when member0_.age=? then ? when member0_.age=? then ? else '기타' end as col_0_0_ from member member0_
/* select case when member1.age = 101 then '열살'2 when member1.age = 203 then '스무살'4 else '기타' end
from Member member1 */
select
case when member0_.age = NULL then ? when member0_.age = ? then ?
    else '기타'
end as col_0_0_ from member member0_;

 

 

2) 복잡한 조건

    @Test
    public void  complexCase(){
        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();

        for(String s :result){
            System.out.println("s = " + s);
        }
    }

 

쿼리 출력 결과

/* select
        case 
            when (member1.age between ?1 and ?2) then ?3 
            when (member1.age between ?4 and ?5) then ?6 
            else '기탕' 
        end 
    from
        Member member1 */ select
            case 
                when member0_.age between ? and ? then ? 
                when member0_.age between ? and ? then ? 
                else '기탕' 
            end as col_0_0_ 
        from
            member member0_

 

 

3) orderBy에서 Case 문 함께 사용하기 예제

 

> 참고: 강의 이후 추가된 내용입니다.


예를 들어서 다음과 같은 임의의 순서로 회원을 출력하고 싶다면?


1. 0 ~ 30살이 아닌 회원을 가장 먼저 출력
2. 0 ~ 20살 회원 출력
3. 21 ~ 30살 회원 출력

 

 

    NumberExpression<Integer> rankPath = new CaseBuilder()
                .when(member.age.between(0, 20)).then(2)
                .when(member.age.between(21, 30)).then(1)
                .otherwise(3);
        List<Tuple> result = queryFactory
                .select(member.username, member.age, rankPath)
                .from(member)
                .orderBy(rankPath.desc())
                .fetch();
        for (Tuple tuple : result) {
            String username = tuple.get(member.username);
            Integer age = tuple.get(member.age);
            Integer rank = tuple.get(rankPath);
            System.out.println("username = " + username + " age = " + age + " rank = "
                    + rank);
        }

 

 

Querydsl은 자바 코드로 작성하기 때문에 rankPath 처럼 복잡한 조건을 변수로 선언해서 select 절, orderBy 절에서 함께 사용할 수 있다.

 

결과
username = member4 age = 40 rank = 3
username = member1 age = 10 rank = 2
username = member2 age = 20 rank = 2
username = member3 age = 30 rank = 1

 

 

 

 

 

 

 

 

 

21.상수, 문자 더하기

 

강의 :

https://www.inflearn.com/course/lecture?courseSlug=querydsl-실전&unitId=30134&tab=curriculum

 

상수가 필요하면 Expressions.constant(xxx) 사용

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

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

 

쿼리 출력

 /* select
        member1.username 
    from
        Member member1 */ select
            member0_.username as col_0_0_ 
        from
            member member0_

 

참고: 위와 같이 최적화가 가능하면 SQL에 constant 값을 넘기지 않는다. 상수를 더하는 것 처럼 최적화가
어려우면 SQL에 constant 값을 넘긴다.

 

 

문자 더하기 concat

   @Test
    public void concat(){
        //{username}_{age}
        List<String> result = queryFactory.select(
                        member.username.concat("_")
                                .concat(member.age.stringValue())
                )
                .from(member)
                .fetch();
        for (String s :result){
            System.out.println("s = " + s);
        }
    }

 

쿼리출력=>

 /* select
        concat(concat(member1.username,
        ?1),
        str(member1.age)) 
    from
        Member member1 */ select
            ((member0_.username||?)||cast(member0_.age as character varying)) as col_0_0_ 
        from
            member member0_

 

결과: member1_10

 


> 참고: member.age.stringValue() 부분이 중요한데, 문자가 아닌 다른 타입들은 stringValue() 로


문자로 변환할 수 있다. 이 방법은 ENUM을 처리할 때도 자주 사용한다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

spring

 

about author

PHRASE

Level 60  라이트

군자는 자기의 처지에 충실함과 동시에 남의 영역을 침범해서 쓸데없이 간섭하거나 말참견을 하는 것을 삼가야 한다. 증자가 한 말. -논어

댓글 ( 4)

댓글 남기기

작성