자바생
article thumbnail
728x90

JPQL & Querydsl

 

JPQL

@Test
@DisplayName("jpql 사용")
void startJPQL() throws Exception {
    Member findMember = em.createQuery("select m from Member m " +
                    "where m.username=:username", Member.class)
            .setParameter("username", "member1")
            .getSingleResult();

    assertThat(findMember.getUsername()).isEqualTo("member1");
}

 

Querydsl

    @Test
    @DisplayName("querydsl 사용")
    void startQuerydsl() throws Exception{
        JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(em);

//        QMember m = new QMember("m"); 생성 방법 1
        QMember m = QMember.member; // 생성 방법 2

        Member findMember = jpaQueryFactory
                .select(m)
                .from(m)
                .where(m.username.eq("member1"))
                .fetchOne();

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

 

Querydsl을 사용하기 위해서는 JPAQueryFactory 객체를 생성해야한다

 

Querydsl과 JPQL 차이

JPQL은 문자열로 이뤄지고(쿼리문), Querydsl은 코드로 이뤄져있다

따라서 JPQL은 쿼리문을 작성할 때, 잘못 작성할 경우 런타임 에러가 발생하고, Querydsl은 "컴파일 에러"가 발생한다

 

Q 클래스 static import 사용

    @Test
    @DisplayName("QType 활용 using static import")
    void QTypeWithStaticImport() throws Exception{

//        Member findMember = queryFactory
//                .select(QMember.member)
//                .from(QMember.member)
//                .where(QMember.member.username.eq("member1"))
//                .fetchOne();

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

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

 

Q 클래스를 생성하는 방법에는 두 가지가 존재한다

미리 Q 클래스에서 static final로 객체를 생성하므로 위 주석에서의 코드와 같이 사용할 수 있다

 

코드를 간결하게 하기 위해 QMember를 static import 하여 코드를 더욱 간결하게 만들 수 있다

 

검색 조건 쿼리

 

Querydsl은 JPQL이 제공하는 조건 쿼리는 전부 가지고 있다

    @Test
    void search() throws Exception {
//        Member findMember = queryFactory
//                .selectFrom(member)
//                .where(member.username.eq("member1")
//                        .and(member.age.eq(10)))
//                .fetchOne();

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

        assertThat(findMember.getUsername()).isEqualTo("member1");
    }

특별하게 and를 사용할 때, 주석 코드처럼 .and를 사용할 수 있지만 파라미터로 넘길 수 있다

 

and를 사용할 때 왜 파라미터 형식으로 넘길 수 있을까?

where의 파라미터 형식을 보면 Predicate "가변인자"를 받기 때문에 가능하다

 

결과 조회

결과를 조회하는 메서드는 여러 종류가 있다

 

fetch() 

fetch를 사용하면 리스트를 받아온다

만약 일치하는 data가 없으면 "빈 리스트"를 반환한다

@Test
@DisplayName("fetch() 사용")
void usingFetch() throws Exception{

    List<Member> noData = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member0"))
            .fetch();

    List<Member> oneData = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member1"))
            .fetch();

    assertThat(noData.size()).isEqualTo(0);
    assertThat(oneData.size()).isEqualTo(1);
}

 

fetchOne() 

fetchOne을 사용하여 결과가 없으면 null, 결과가 둘 "이상"이면 NonUni~~ 에러를 발생시킨다

 

@Test
@DisplayName("fetchOne() 사용")
void usingFetchOne() throws Exception{
    Member noMember = queryFactory
            .selectFrom(member)
            .where(member.username.eq("member0"))
            .fetchOne();

    assertThat(noMember).isNull();

    assertThrows(NonUniqueResultException.class,
            () -> queryFactory
                    .selectFrom(member)
                    .where(member.username.eq("member1").or(member.username.eq("member2")))
                    .fetchOne());
}

 

fetchFist()

sql의 limit 을 사용한다

 

fetchResults()

fetchResults의 반환 클래스는 "QueryResults"이다

 

@Test
@DisplayName("fetchResults() 사용")
void usingFetchResults() throws Exception{
    QueryResults<Member> results = queryFactory
            .selectFrom(member)
            .where(member.age.goe(10))
            .fetchResults();

    System.out.println("results.getTotal( = " + results.getTotal());
    assertThat(results.getResults().size()).isEqualTo(4);
    assertThat(results.getTotal()).isEqualTo(4);
}

 

QueryResults에는 페이징에 필요한 정보들이 들어있다

total 뿐만 아니라 limit, offset이 있고, results는 결과값을 가지는 List이다

 

앞서 spring data jpa에서 페이징을 사용하기 위해 "Page" 객체를 사용하는 것처럼 count 쿼리가 나가게 된다 

 

fetchCount()

@Test
@DisplayName("fetchCount() 사용")
void usingFetchCount() throws Exception{
    long count = queryFactory
            .selectFrom(member)
            .where(member.age.goe(10))
            .fetchCount();

    assertThat(count).isEqualTo(4);
}

 

정렬

@Test
@DisplayName("정렬")
void sort() throws Exception{
    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);

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

    assertThat(member5.getUsername()).isEqualTo("member5");
    assertThat(member6.getUsername()).isEqualTo("member6");
    assertThat(memberNull.getUsername()).isNull();
    assertThat(result2.get(0).getUsername()).isNull();
}

orderBy를 사용하여 정렬을 할 수 있다

 

조건 검색 쿼리에서 and는 가변인자를 통해 여러 개의 조건들을 파라미터로 전달할 수 있었는데,

orderBy도 여러 가지 "정렬 조건"들을 파라미터로 전달할 수 있다

또한, nullsLast()와 nullsFirst()를 통해 null 값은 정렬을 할 때 제일 앞에 놓을지, 마지막에 놓을지 결정할 수 있다

 

 

페이징

@Test
@DisplayName("페이징")
void paging1() throws Exception{
    List<Member> result = queryFactory
            .selectFrom(member)
            .orderBy(member.username.asc())
            .offset(1)
            .limit(2)
            .fetch();

    assertThat(result.size()).isEqualTo(2);
    assertThat(result.get(0).getUsername()).isEqualTo("member2");
    assertThat(result.get(1).getUsername()).isEqualTo("member3");
}

offset은 "시작 index"(0부터 시작)를 의미하고, limit는 "한 페이지 당 데이터의 갯수"를 정하는 메서드이다

위 코드는 시작 index를 두번째로 정하고, 각 페이지당 데이터를 2개만 저장한다

 

member1, member2, member3, member4가 저장되있는데 offset이 1이고, 최대 2개의 데이터를 조회하니

member2, member3이 나오는 것을 알 수 있다

 

만약 전체 조회 수가 필요하다면 앞서 말했던 "fetchResults()"를 사용하면 된다

 

집합

@Test
@DisplayName("집합")
void aggregation() throws Exception{
    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);
    assertThat(tuple.get(member.count())).isEqualTo(4);
    assertThat(tuple.get(member.age.sum())).isEqualTo(100);
    assertThat(tuple.get(member.age.avg())).isEqualTo(25);
    assertThat(tuple.get(member.age.max())).isEqualTo(40);
    assertThat(tuple.get(member.age.min())).isEqualTo(10);
}
  • member.count는 long, avg는 double
  • Tuple이라는 class를 사용하여 표현함

 

GroupBy 사용

팀의 이름과 각 팀의 평균 연령을 어떻게 구할까?

  • 팀 이름으로 group을 짓고, group의 평균 연령을 구하면 된다
@Test
@DisplayName("팀의 이름과 각 팀의 평균 연령을 구하기")
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);

    assertThat(teamA.get(team.name)).isEqualTo("teamA");
    assertThat(teamA.get(member.age.avg())).isEqualTo(15);

    assertThat(teamB.get(team.name)).isEqualTo("teamB");
    assertThat(teamB.get(member.age.avg())).isEqualTo(35);
}

 


REFERENCES

실전! Querydsl (김영한 님)

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

728x90

'Spring 강의 > QueryDSL' 카테고리의 다른 글

순수 JPA Repo + Querydsl, 동적 쿼리  (0) 2022.05.14
수정, 삭제 벌크 연산  (0) 2022.05.13
프로젝션과 결과 반환, 동적 쿼리  (0) 2022.05.12
QueryDsl 조인 ~  (0) 2022.05.02
profile

자바생

@자바생

틀린 부분이 있다면 댓글 부탁드립니다~😀

검색 태그