JPA

QueryDSL 사용하는 조회 예제

빈코 2022. 10. 28. 14:11

이전 포스팅에서는 QueryDSL을 적용하고 도메인 모델을 살펴본 후 각 엔티티들을 설계하였다. 오늘은 기존 JPQL과의 차이를 살펴보면서 조회하는 예제를 만들어보려 한다.

로고
Query DSL

 

개요

엔티티 클래스

이전 포스팅에서 도메인 모델과 엔티티 클래스를 설계하였습니다. 해당 엔티티 코드는 이전 포스팅에 있으며, 따라 하지 않는 분들은 흐름만 파악하셔도 충분합니다. Member와 Team은 다대일 양방향 관계로 매핑되어 있습니다😄

 

예제 코드📔

@SpringBootTest
@Transactional
public class QuerydslBasicTest {

    @Autowired
    EntityManager em;

    JPAQueryFactory queryFactory;

    @BeforeEach
    public void before() {
        queryFactory = new JPAQueryFactory(em);
        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);
    }
 }

 

테스트 코드를 작성하기에 앞서, 저는 h2 데이터베이스를 사용하였고 매번 테스트마다 given 단계에서 가데이터를 넣지 않기 위해 위의 코드처럼 @BeforeEach 어노테이션을 사용하여 테스트 진행 직전에 가데이터를 넣었습니다.

 

MEMBER_ID NAME AGE TEAM_NAME
1 member1 10 teamA
2 member2 20 teamA
3 member3 30 teamB
4 member4 40 teamB

 

JPQL📗

@Test
public void startJPQL() {
    // member1을 찾아라
    Member findByJPQL = em.createQuery("select m from Member m where m.username = :username"
    		, Member.class)
            .setParameter("username", "member1")
            .getSingleResult();

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

기존에 사용하던 JPQL을 사용하여 member1을 찾는 조회 예제를 만들었습니다. JPQL은 직접 String 형식으로 SQL문을 작성해야 하는 단점이 있습니다. 단점인 이유는 JPQL은 기본적으로 런타임 시에 오류가 발생했는지 알 수 있습니다.

 

만약 JPQL을 사용한 프로젝트를 충분한 테스트 없이 서버에 배포했다면요? 사용자가 직접 오류를 겪는 큰 문제가 발생합니다.

 

직접 String으로 쿼리문을 작성할 때 오타가 발생할 수 있으며, 실행을 해야만 잘못된 걸 알 수 있습니다. 그렇다면 Query DSL은 어떤 장점이 있을까요?

 

Query DSL📒

Q클래스를 사용하는 방법은 총 2가지로 나뉘는데, 개발 및 포스팅 편의 상 static으로 빼서 예제 코드를 진행하겠습니다😅

 

📌Q클래스 인스턴스를 사용하는 2가지 방법. 같은 테이블을 조인해야 하는 경우가 아니면 기본 인스턴스를 사용하는 것을 추천드립니다.

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

 

📌static 메서드로 빼서 진행

import static study.querydsl.entity.QMember.member;
import static study.querydsl.entity.QTeam.team;

@Test
public void startQuerydsl() {
    Member findMember = queryFactory
            .select(member)
            .from(member)
            .where(member.username.eq("member1"))
            .fetchOne();

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

 

Query DSL 예제 또한 member1을 찾는 로직으로 구성하였습니다. Query DSL을 사용하기 위해선 먼저 Query DSL이 제공하는 JPAQueryFactory 클래스의 인스턴스를 생성하고(@BeforeEach 단계에서 생성) 사용해야 합니다.

 

Query DSL은 Criteria와 같이 JPQL 빌더 역할을 하기 때문에 결론적으로 JPQL을 생성하여 조회합니다.

 

하지만 JPQL 직접 작성과는 큰 차이를 나타냅니다. 위 코드를 보면 직관적이기 때문에 이해하기도 쉽고, 파라미터 바인딩 또한 자동으로 처리해주기 때문에 따로 매핑해 줄 필요도 없습니다.

 

가장 큰 장점으로는 Query DSL은 쿼리에 오류가 있다면 컴파일 시점에서 오류를 발견할 수 있습니다. 즉, 개발 단계에서 오류를 확인할 수 있는 장점입니다.

 

컴파일 오류
컴파일 오류


@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");
}

 

@Test
public void searchAndParam() {
    Member findMember = queryFactory
            .selectFrom(member)
            .where(
                    member.username.eq("member1"),(member.age.eq(10))
            )
            .fetchOne();

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

검색조건은 and() 연산자나 , 를 사용해서 구현할 수 있습니다. 만약 파라미터가 null 값으로 들어오면 무시하기 때문에, 메서드 추출을 활용하여 동적 쿼리를 깔끔하고 간결하게 구현할 수 있습니다.

 

물론 or() 연산자도 동일하게 작동됩니다.

 

📌이 밖에 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%’ 검색

 

📌Query DSL이 제공하는 기본적인 쿼리 메서드

  • select()
  • from()
  • selectFrom() : select하는 엔티티와 from의 엔티티가 일치한 경우는 합칠 수 있다.
  • where()
  • update()
  • set()
  • delete()

 

📌쿼리 완성 후 결과 조회 시에는 다음과 같은 메서드를 이용할 수 있습니다.

  • fetch() : 리스트 조회, 데이터 없으면 빈 리스트 반환
  • fetchOne() : 단 건 조회, 결과 없으면 null, 결과가 2개 이상이면 NonUniqueResultException 발생
  • fetchFirst() : 첫 번째 결과 조회
  • fetchResults() : 페이징 정보 포함 및 totalCount 쿼라를 추가 실행
  • fetchCount() : count 쿼리로 변경해서 count 수를 조회할 수 있다

 


👨‍👩‍👦‍👦 오픈채팅방 운영

취업을 준비하는 예비 개발자분들을 위한 질문&답변할 수 있는 공간을 만들었습니다. 취업과 이직을 하기 위해서 어떤 걸 중점적으로 준비해야 하는지부터 포트폴리오&이력서 작성법 등 다양한 질문들을 받고 답변을 드립니다. 참여하셔서 다양한 정보 얻고 가시면 좋을 것 같네요😁

 

참여코드 : 456456

https://open.kakao.com/o/gVHZP8dg

 

비전공 개발자 취업 준비방(질문&답변)

#비전공 #개발자 #취업 #멘토링 #부트캠프 #국비지원 #백엔드 #프론트엔드 #중소기업 #중견기업 #자바 #Java #sql

open.kakao.com

 


👨‍💻 전자책 출간

아울러 제가  🌟비전공자에서 2년만에 보안 전문 중견기업으로 이직 한 방법들을 정리한 전자책을 출간 하게 되었습니다. 어떤 걸 공부해야 하는지, 이직을 위해서 무엇을 준비해야 하는지, 제가 받았던 기술 면접 리스트 등 다양한 목차로 구성되어 있습니다. 또한, 구매 시 1:1 채팅을 이용하여 포트폴리오 첨삭을 도와드리고 있습니다. 🐕전자책으로 얻은 모든 수익은 유기견 센터 '팅*벨 입양센터'에 후원될 예정입니다. 관심 있으신 분들은 아래 링크를 참고해주세요😁

https://kmong.com/gig/480954

 

비전공개발자 2년만에 중견기업 들어간 방법 | 14000원부터 시작 가능한 총 평점 0점의 전자책, 취

0개 총 작업 개수 완료한 총 평점 0점인 Binco의 전자책, 취업·이직 전자책 서비스를 0개의 리뷰와 함께 확인해 보세요. 전자책, 취업·이직 전자책 제공 등 14000원부터 시작 가능한 서비스

kmong.com


 

마치며

지금까지 Query DSL이 제공하는 방식으로 조회 예제를 만들어 보았습니다. 다음 포스팅은 정렬, 페이징, 집합 관련 로직을 간단한 예제를 통해 구현해볼게요😀

 

QueryDSL Series

1. QueryDSL 적용 및 예제 시리즈 시작

2. QueryDSL 조회 예제(현재)

3. QueryDSL 정렬 & 페이징 & 집합

4. QueryDSL 조인 예제

5. QueryDSL Projection

6. QueryDSL 동적 쿼리 & Bulk 연산

7. QueryDSL 실무 활용

 

Reference

실전! Querydsl

 

반응형