코드로 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 |