Reference. 자바 ORM 표준 JPA 프로그래밍
책 목차 및 이전 글
더보기
들어가기 전 JPA 특징, Q&A
1. JPA 소개
3. 영속성 관리
4. 엔티티 매핑
4.1 - 4.3 @Entity, @Table, 다양한 매핑
4.4 - 4.5 데이터베이스 스키마 자동 생성, DDL 생성 기능
5. 연관관계 매핑 기초
6. 다양한 연관관계 매핑
7. 고급매핑
8. 프록시와 연관관계 관리
9. 값 타입
9.3~5 값 타입과 불변 객체, 값 비교, 값 타입 컬렉션
10. 객체지향 쿼리 언어
10.4 QueryDSL
- Criteria의 복잡하고 어렵다는 단점을 개선하기 위한 프로젝트가 QueryDSL
- QueryDSL은 JPQL 빌더 역할을 하며, JPA Criteria를 대체 가능
10.4.1 QueryDSL 설정
필요 라이브러리
pom.xml 추가
querydsl-jpa
: QueryDSL JPA 라이브러리
querydsl-apt
: 쿼리 타입(Q)을 생성할 때 필요한 라이브러리
<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>
환경설정
- QueryDSL을 사용하려면 Criteria의 메타 모델처럼 엔티티 기반 쿼리 타입이라는 쿼리용 클래스 생성이 필요, 쿼리 타입 생성용 플러그인 추가가 필요
<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
경로에QMember.java
처럼 Q로 시작하는 쿼리 타입들이 생성
- 콘솔에
10.4.2 시작
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("회원"))
.orderBy(qMember.name.desc())
.list(qMember); // fetch
}
/* JPQL:
select m from Member m
where m.name = ?1
order by m.name desc
*/
기본 Q 생성
- 쿼리 타입(Q)은 사용하기 편리하도록 인스턴스를 보관, 같은 엔티티를 조인하거나 같은 엔티티를 서브쿼리에 사용하는 경우 별칭이 필요
QMember qMember = new QMember("m"); //직접 지정 QMember qMember = QMember.member; //기본 인스턴스 사용
- 위에 소스를 기본 인스턴스를 사용하면 간결하게 작성이 가능
import static jpabook.jpashow.doamin.QMember.member; //기본 인스턴스 public void basic() { EntityManager em = emf.createEntityManager(); JPAQuery query = new JPAQuery(em); List<Member> members = query.from(member) .where(member.name.eq("회원")) .orderBy(member.name.desc()) .list(member); // fetch }
10.4.3 검색 조건 쿼리
- QueryDSL의 where절에는 and나 or 사용 가능
.where(item.name.eq("좋은상품"), item.price.gt(200000))
의 경우 and로 연산 됌
//QueryDSL
JPAQuery query = new JPAQuery(em);
QItem item = QItem.item;
List<Item> list = query.from(item)
.where(item.name.eq("좋은상품").and(item.price.gt(20000)))
.list(item);
//실행된 JPQL
select item
from Item item
where item.name = ?1 and item.price >?2
- where() 메소드에서 사용할 수 있는 예시들
item.price.between(10000, 20000); //가격이 10000원 ~ 20000원 상품 item.name.contains("상품1"); //상품1이라는 이름을 포함한 상품 (like '%상품1%') item.name.startWith("고급"); //이름이 고급으로 시작하는 상품 (like '고급%')
10.4.4 결과 조회
- 보통
uniqueResult()
나list()
를 사용하여 대상을 조회 결과 조회 API는 com.mysema.query.Proejctable에 정의uniqueResult()
: 조회 결과가 한 건일 때 사용 조회 결과가 없으면 null을 반환하고 결과가 하나 이상이면NonuniqueResultException
발생
singleResult()
: uniqueResult()와 동일, 결과가 하나 이상이면 처음 데이터 반환 (fetchOne)
list()
:결과가 하나 이상일 떄 사용, 결과가 없으면 빈 컬렉션 반환 (fetch)
10.4.5 페이징과 정렬
- 정렬은 orderBy를 사용하고 쿼리 타입(Q)이 제공하는 asc(), desc()를 사용
- 페이징은 offset과 limit을 적절히 조합해서 사용
- restrict() 메소드에 QueryModifers를 파라미터를 사용해도 동일 작동
QItem item = QItem.item;
//기본 사용법
query.from(item)
.where(item.price.gt(20000))
.orderBy(item.price.desc(), item.stockQuantity.asc())
.offset(10).limit(20)
.list(item);
//restrict 사용
QueryModifiers queryModifiers = new QueryModifiers(20L, 10L);
List<Item> list = query.from(item)
.restrict(queryModifiers)
.list(item);
- 실제 페이징 처리를 하려면 검색 데이터 수가 필요 그럴 경우
listResults()
를 사용SearchResults<Item> result = query.from(item) .where(item.price.gt(20000)) .offset(10).limit(20) //offset은 시작점, limit는 가져오는 갯수 .listResults(item) //fetchResults로 메소드가 변경 됌
10.4.6 그룹
- 그룹은 groupBy를 사용하고, 그룹화된 결과를 제한할 경우 having을 사용
query.from(item) .groupBy(item.price) .having(item.price.gt(1000)) .list(item);
10.4.7 조인
- 조인은
innerJoin(join)
,leftJoin
,rightJoin
,fullJoin
사용 가능 추가로 JPQL의 on과 성능 최적화를 위한 fetch 조인도 사용 가능
- 기본 문법은 첫 번째 파라미터엔 조인 대상, 두 번째 파라미터엔 별칭으로 쓸 쿼리 타입 지정
//기본 조인 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); //조인 on 사용 query.from(order) .leftJoin(order.orderItems, orderItem) .on(orderItem.count.gt(2)) .list(order); //페치 조인 사용 방법 query.from(order) .innerJoin(order.member, member).fetch() .leftJoin(order.orderItems, orderItem).fetch() .list(order); //세타 조인 방법 query.from(order, member) .where(order.member.eq(member)) .list(order);
10.4.8 서브 쿼리
- 서브 쿼리는 JPASubQuery를 생성해서 사용
- 서브 쿼리의 결과가 하나면
unique()
, 여러 건이면list()
를 사용//서브쿼리 - 한건 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); //서브쿼리 - 여러건 query.from(item) .where(item.in( new JPASubQuery().from(itemSub) .where(item.name.eq(itemSub.name)) .list(itemSub) )) .list(item);
10.4.9 프로젝션과 결과 반환
- 프로젝션: select 절에 조회 대상을 지정하는 것을 의미
프로젝션 대상이 하나
QItem item = QItem.item;
List<String> result = query.from(item).list(item.name);
for (String name : result) {
System.out.println("name = " + name;
}
여러 컬럼 반환과 튜플
- 여러 필드를 선택하면 QueryDSL의 기본으로 Tuple이라는 Map과 비슷한 내부 타입 사용
- 조회 결과는 tuple.get() 메소드에 조회한 쿼리 타입을 지정
QItem item = QItem.item; List<Tuple> result = query.from(item).list(item.name, item.price); //List<Tuple> result = query.from(item).list(new QTuple(item.name, item.price)); for (Tuple tuple : result){ System.out.println("name = " + tuple.get(member.name)); System.out.println("age = " + tuple.get(member.age)); }
빈 생성
- 쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶은 경우 빈 생성 기능을 사용
- 프로퍼티 접근
- 필드 직접 접근
- 생성자 사용
//엔티티가 아닌 DTO 생성 public class ItemDTO { private String username; private int pirce; public ItemDTO() {} public ItemDTO(String username, int price) { this.username = username; this.price = price; } //Getter, Setter ... }
프로퍼티 접근 (Setter)
- ItemDTO에 경우 username을 가지고 있기 떄문에 as를 사용해서 별칭을 지정
QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.bean(ItemDTO.class, item.name.as("username"), item.price));
필드 직접 접근
- 필드에 직접 접근해서 값을 설정, 필드를 private로 설정해도 동작
QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.fields(ItemDTO.class, item.name.as("username"), item.price));
생성자 사용
- 지정한 프로젝션과 파라미터 순서가 같은 생성자가 필요
QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.constructor(ItemDTO.class, item.name.as("username"), item.price));
DISTINCT
query.distinct().from(item)...
10.4.10 수정, 삭제 배치 쿼리
- QueryDSL도 수정, 삭제 같은 배치 쿼리를 지원
- JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리
수정 배치 쿼리
QItem item = QItem.item;
JPAUpdateClause updateClause = new JPAUpdateClause(em, item);
long count = updateClause.where(item.name.eq("jpa 책"))
.set(imte.price, item.price.add(100))
.execute();
삭제 배치 쿼리
QItem item = QItem.item;
JPADeleteClause deleteClause= new JPADeleteClause(em, item);
long count = deleteClause.where(item.name.eq("jpa 책"))
.execute();
10.4.11 동적쿼리
- BooleanBuilder를 사용하면 특정 조건에 따른 동적 쿼리를 편리하게 생성 가능
SearchParam param = new SearchParam(); param.setName("시골개발자"); param.setPrice(10000); QItem item = QItem.item; BooleanBuilder builder = new BooleanBuilder(); if (StringUtils.hasText(param.getName())) { builder.and(item.name.contains(param.getName())); } if (param.getPrice() != null) { builder.and(item.price.gt(param.getPrice())); } List<Item> result = query.from(item) .where(builder) .list(item);
10.4.12 메소드 위임
- 메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건을 직접 정의가 가능
public class ItemExpression { @QueryDelegate public static BooleanExpression isExpensive(QItem item, Integer price) { return item.price.gt(price); } }
쿼리 타입에 생성된 결과
... public class QItem extends EntityPathBase<Item> { ... public BooleanExpression isExpensive(Integer price) { return ItemExpression.isExpensive(this, price); } }
'개발서적 > 자바 ORM 표준 JPA' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 10.6 객체지향 쿼리 심화 (0) | 2021.09.13 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 10.5 네이티브 SQL (0) | 2021.09.13 |
[자바 ORM 표준 JPA 프로그래밍] 10.3 Criteria (0) | 2021.09.13 |
[자바 ORM 표준 JPA 프로그래밍] 10.2 JPQL (0) | 2021.09.13 |
[자바 ORM 표준 JPA 프로그래밍] 10.1 객체체지향 쿼리 소개 (0) | 2021.09.13 |