본문 바로가기

IT/Spring JPA

QueryDSL 사용법

반응형

코드로 JPQL을 작성하므로 문법 오류를 컴파일 단계에서 잡을 수 있음 (Cirteria의 장점)
쉽고 간결하며 코드를 통해 쿼리문을 바로 생각해낼 수 있음. (JPQL String의 장점)

<사전 작업>

1. 필요 라이브러리 추가 

<dependency>
	<groupId>com.mysema.querydsl</groupId>
	<artifactId>querydsl-jpa</artifactId>
	<version>3.6.3</version>
</dependency>

<dependency>
	<groupId>com.mysema.querydsl</groupId>
	<artifactId>querydsl-apt</artifactId>
	<version>3.6.3</version>
	<scope>provided</scope>
</dependency>

2. 엔티티를 기반으로 쿼리 타입용 쿼리 클래스를 생성해야 됨.

<build>
        <plugins>
            <plugin>
                <groupId>com.mysema.maven</groupId>
                <artifactId>apt-maven-plugin</artifactId>
                <version>1.1.3</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>process</goal>
                        </goals>
                        <configuration>
                            <outputDirectory>target/generated-sources/java</outputDirectory>
                            <processor>com.mysema.query.apt.jpa.JPAAnnotationProcessor</processor>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

콘솔에서 mvn compile을 통해 outputDirectory에 지정된 경로에 Q로 시작하는 쿼리 타입들이 생성된다.
이후 소스 경로에 target/generated-sources를 추가하면 된다.

 


<사용법>


1. 기본
먼저, QueryDsl을 사용하기 위해 JPAQuery 객체를 생성해야 된다. 이때 엔티티 매니저를 생성자에 넘겨준다. 
그다음 쿼리 타입을 생성하고 이때 같은 엔티티를 조인하거나, 서브 쿼리로 사용할 수 있기에 별칭을 지정해서 생성하길 권장한다.

 

public void queryDSL(){
        EntityManager em = emf.createEntityManager();
        JPAQuery query = new JPAQuery(em);
        
        QMember qMember = new QMember("m"); //생성되는 JPQL의 별칭 m
        List<Member> members = query.from(qMember)
                                    .where(qMember.name.eq("회원1"))
                                    .orderBy(gMember.name.desc())
                                    .list(gMember);
                    
    }
    

select * from member where name = "회원1" orderBy name desc;

 

별칭을 사용 X 경우 QMember qMember = QMember.member (기본 인스턴스를 사용)
별칭을 사용 O 경우 QMember qMember = new QMember("m") (별칭을 생성자에 넘겨줌)

기본 인스턴스를 사용할 경우 static을 통해 코드를 더 간결하게 만들 수 있다.

import static jpabook.jpashop.domain.QMember.member;


    public void queryDSL(){
        EntityManager em = emf.createEntityManager();
        JPAQuery query = new JPAQuery(em);

        List<Member> members = query.from(Member)
                .where(Member.name.eq("회원1"))
                .orderBy(Member.name.desc())
                .list(Member);

    }

 

2. 검색 조건 쿼리

JPAQuery query = new JPAQuery();
QItem item = QItem.item;
List<Item> items = query.from(item)
                        .where(item.name.eq("좋은 상품").and(item.price.qt(2000)))
                        .list(item);

select * from item where name = "좋은 상품" and price > 2000;

 

where절에 and, or을 사용할 수 있음.
또한, contains(= like "%문자열%"), startWith(= like "문자열%")으로 like 문을 사용할 수 있다.

3. 결과 조회
uniqueResult() = 조회가 1건인 경우 사용. 없으면 Null, 1개 이상인 경우 NonUniqueResultException예외 발생
singleResult() = 결과가 1개 이상이면 맨 위 데이터를 반환
list() = 조회가 1건 이상인 경우 사용. 없으면 빈 컬랙션 반환

4. 페이징과 정렬 
정렬은 OrderBy()를 사용한다.
페이징은 offset(), limit()을 사용해서 페이징 처리를 할 수 있다.

JPAQuery query = new JPAQuery();
QItem item = QItem.item;
List<Item> items = query.from(item)
						.where(item.name.eq("좋은 상품").and(item.price.qt(2000)))
						.orderBy(item.price.desc(), item.stockQuantity.asc())
						.offset(10).limit(20)
						.list(item);

 

실제 페이징처리를 위해서는 아래와 같이 구현하는 것이 바람직하다.

SearchResults<Item> result = 
         query.from(item)
              .where(item.price.gt(10000))
              .offset(10).limit(20)
              .listResults(item);
    
long total = result.getTotal();//전체 데이터 수 
long limit = result.getLimit();
long offset = result.getOffset();

List<Item> results = result.getResults();//조회된 데이터

listResults()를 사용하면 전체 데이터의 count 쿼리를 한 번 더 수행한다. 
그래서 getTotal()을 통해 전체 데이터 수를 조회할 수 있다.

5. 그룹
groupBy(), having()을 통해 그룹화된 결과를 조회하고 제한할 수 있다.

6. 조인
innerJoin(join), leftJoin, rightJoin, fullJoin을 사용할 수 있다. 
또한 on과 JPA에서 성능 최적화를 위해 제공하는 fetch 조인 역시 사용할 수 있다.

1) 기본 조인

QOrder order = QOrder.order;
QMember member = QMember.member;
QOrderItem orderItem = QOrderItem.orderItem;

query.from (order)
	 .join(order.member, member)
	 .leftJoin(order.orderItems, orderItem)
	 .list(order);

select * 
from order 
join member on order.member_id = member.id
left join orderItem on order.id in orderItem.order_id

 

※ 배경 지식
On에 조건을 걸면 where절로 조건을 거는 것보다 성능이 빨라진다. 
[이유]
On에 조건을 걸면 미리 조인되기 전에 해당 테이블의 데이터를 필터링 한 뒤에 조회된 데이터들에 대해서 조인이 이뤄진다. 하지만, where절에 조건을 걸면 전체 데이터에 대한 조인이 이뤄진 다음에 필터링 조건이 수행됨으로 불필요한 데이터의 검증을 한 번 더 수행되기 때문에 성능이 떨어지게 된다.

2) 조인 ON 사용

query.from(order) 
 .leftJoin(order.orderItems, orderItem) 
 .on(order.orderItems.count.gt(2)) 
 .list(order); 


 
3) 패치 조인 사용 (조인된 테이블 데이터에 지연 로딩이 설정되어 있더라고 한 번에 조회하는 조인 문임)

query.from(order) 
 .innerJoin(order.member, member).fetch() 
 .leftJoin(order.orderItems, orderItem).fetch() 
 .list(order); 

 

※ 배경 지식
일반조인 VS 패치 조인
=> JPQL
select o from order o inner Join order.member m

=> 실행 SQL
select o.id, o.member_id, o.delivery_id, o.order_date, o.status from order Inner Join member on order.member_id = member.id;

=> JPQL
select o from order o Join Fetch order.member m

=> 실행 SQL
select  o.*, m.* from order o inner join member m on o.member_id = m.id
-> 조인된 member 테이블의 컬럼 내용도 가져온다.

전체를 사용하지 않는 엔티티의 내용까지 로딩하므로 성능에 악영향을 미칠 수 있으나, SQL 호출 횟수를 줄여 성능 최적화할 수 있다.

 

 

4) from 절에 여러 조건 사용법

QOrder order = QOrder.order;
QMember member = QMember.member;

query.from (order, member)
	 .where(order.member.eq(member)
	 .list(order);

 

7. 서브 쿼리
com.mysema.query.jpa.JPASubQuery를 생성한다.
서브 쿼리의 결과가 하나이면 unique(), 여러 개이면 list()를 사용한다.

unique() 사용 예시

QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");

query.from(item)
	 .where(item.price.eq(
			new JPASubQuery().from(itemSub).unique(itemSub.price.max())))
	 .list(item);
	 
QItem item = QItem.item;
QItem itemSub = new QItem("itemSub");

 

select * from item where price = (select max(price) from item);


list() 사용 예시

query.from(item)
	 .where(item.name.eq(
			new JPASubQuery().from(itemSub)
							 .where(item.name.eq(itemSub.name))
							 .list(itemSub))
	 .list(item);

select * from item where name in (select name from item);

 

9. 프로젝션과 결과 반환

프로젝션 대상이 하나
이 경우 해당 타입으로 반환 처리해주면 된다.

QItem item = QItem.item;
        
List<String> result = query.from(item).list(item.name);
        
for (String name : result){
	System.out.println("name : " + name);
}


여러 컬럼 반환과 튜플
Tuple이라는 Map과 비슷한 내부 타입을 사용한다.
Tuple.get()에 조회할 쿼리 타입을 지정해주면 된다.

QItem item = QItem.item;
        
List<Tuple> result = query.from(item)
                          .list(item.name, item.price);
        
for(Tuple tuple : result){
	System.out.println("name : "+tuple.get(item.name));
    System.out.println("price : "+tuple.get(item.price));
}
반응형

'IT > Spring JPA' 카테고리의 다른 글

JPA 성능 최적화  (0) 2021.08.13
JPA 예외처리 및 프록시 심화 익히기  (0) 2021.08.01
JPA 영속성 관리  (0) 2021.07.15
JPA 웹 어플리케이션 개발  (0) 2021.07.01
생성자 제한하기  (0) 2020.02.16