티스토리 뷰
| QueryDSL
- 쿼리언어를 코드로 작성할 수 있도록 해주는 오픈소스 프로젝트
- 데이터 조회 기능이 특화
|| 설정
ㅇ 라이브러리 추가 및 환경설정 (pom.xml)
- querydsl-jpa : QueryDSL JPA 라이브러리
1 2 3 4 5 | <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-jpa</artifactId> <version>${querydsl.version}</version> </dependency> | cs |
- querydsl-apt : 쿼리 타입 생성 시 필요한 라이브러리
1 2 3 4 5 6 | <dependency> <groupId>com.querydsl</groupId> <artifactId>querydsl-apt</artifactId> <version>${querydsl.version}</version> <scope>provided</scope> </dependency> | cs |
1 2 3 4 5 | <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> | cs |
- 엔티티를 기반으로 쿼리 타입이라는 쿼리용 클래스를 생성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | <project> <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.querydsl.apt.jpa.JPAAnnotationProcessor</processor> </configuration> </execution> </executions> </plugin> ... </plugins> </build> </project> | cs |
|| 시작
QueryDSL을 사용하기 위해 com.mysema.query.jpa.impl.JPAQuery 객체를 생성해야 함
1 2 3 4 5 6 7 8 9 10 11 12 | public void queryDSL() { EntityManager em = emf.createEntityManager(); JPAQuery query = new JPAQuery(em); QMember qMember = new QMember("m"); // 생성되는 JPAL의 별칭 List<Member> members = query.from(qMember) .where(qMamber.name.eq("회원1")) .orderBy(qMember.name.desc()) .list(qMember); } | cs |
|| 검색 조건 쿼리
- QueryDSL where 절에는 and 나 or 사용 가능
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public void queryDSL() { EntityManager em = emf.createEntityManager(); JPAQuery query = new JPAQuery(em); QItem item = QItem.item; List<Item> list = query.from(item) .where(item.name.eq("좋은상품").and(item.price.gt(20000)) // .where(item.name.eq("좋은상품"), item.price.gt(20000)) // .where(item.price.between(10000, 20000)) // .where(item.name.contains("상품1") // .where(item.name.startWith("고급") .list(item); // 조회할 프로젝션 } | cs |
=> 생성 JPQL
1 2 3 | select item from Item item where item.name = ?1 and item.price > ?2 | cs |
|| 결과 조회
* uniqueResult() : 조회 결과가 한 건일 때 사용,
조회 결과가 없으면 null 반환,
결과가 하나 이상이면 com.querydsl.core.NonUniqueResultException 예외 발생
* singleResult() : uniqueResult()와 같지만 결과가 하나 이상이면 처음 데이터 반환
* list() : 결과가 하나 이상일 때 사용,
결과가 없으면 빈 컬렉션 반환
|| 페이징과 정렬
1.
정렬은 orderBy 사용 (asc(), desc())
페이징은 offset과 limit를 조합
1 2 3 4 5 6 7 | 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); | cs |
2.
com.querydsl.core.QueryModifiers 파라미터 사용
1 2 3 4 | QueryModifiers queryModifiers = new QueryModifiers(20L, 10L); // Limit, offset List<Item> list = query.from(item) .restrict(queryModifiers) .list(item); | cs |
* 실제 페이징 처리를 위해 검색된 전체 데이터 수를 알아야 함.
이때는 list() 대신 listResults() 사용
1 2 3 4 5 6 7 8 9 | 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(); // 조회된 데이터 | cs |
|| 그룹
groupBy 와 having 사용
1 2 3 4 | query.from(item) .groupBy(item.price) .having(item.price.gt(1000)) .list(item); | cs |
|| 조인
첫 번째 파라미터에 조인 대상을 지정하고,
두 번째 파라미터에 별칭으로 사용할 쿼리 타입을 지정
join(조인 대상, 별칭으로 사용할 쿼리 타입)
ㅇ 기본 조인
1 2 3 4 5 6 7 8 | 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); | cs |
ㅇ on을 사용한 조인
1 2 3 4 | query.from(order) .leftJoin(order.orderItems, orderItem) .on(orderItem.count.gt(2)) .list(order); | cs |
ㅇ fetch join
1 2 3 4 | query.from(order) .innerJoin(order.member, member).fetch() .leftJoin(order.orderItems, orderItem).fetch() .list(order); | cs |
ㅇ 세타 조인
1 2 3 4 5 6 | QOrder order = QOrder.order; QMember member = QMember.member; query.from(order, member) .where(order.member.eq(member)) .list(order); | cs |
|| 서브 쿼리
JPASubQuery를 생성하여 사용
결과가 하나면 unique(), 여러 건이면 list() 사용
1 2 3 4 5 6 7 8 | 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) | cs |
ㅇ 서브 쿼리가 여러 건일 경우
1 2 3 4 5 6 7 8 9 10 | QItem item = QItem.item; QItem itemSub = new QItem("itemSub"); query.from(item) .where(item.in( new JPASubQuery().from(itemSub) .where(item.name.eq(itemSub.name) .list(itemSub) )) .list(item); | cs |
|| 프로젝션과 결과 반환
select 절에 조회 대상을 지정하는 것을 프로젝션
||| 프로젝션 대상이 하나
1 2 3 4 5 6 | QItem itme = QItem.item; List<String> result = query.from(item).list(item.name); for(String name : result) { System.out.println(name); } | cs |
||| 여러 컬럼 반환과 튜플
프로젝션 대상으로 여러 필드 선택 시 com.querydsl.core.Tuple 이라는 Map과 비슷한 내부 타입 사용
조회 결과는 tuple.get() 메소드에 조회한 쿼리 타입을 지정
1 2 3 4 5 6 7 8 9 | 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(tuple.get(item.name)); System.out.println(tuple.get(item.price)); } | cs |
||| 빈 생성
쿼리 결과를 엔티티가 아닌 특정 객체로 받고 싶을 경우 빈 생성 기능 사용
QueryDSL의 객체를 생성하는 방법
- 프로퍼티 접근
- 필드 직접 접근
- 생성자 사용
com.querydsl.core.types.Projections 사용
쿼리 결과와 매핑할 프로퍼티 이름이 다를 경우 as(별칭) 사용
ㅇ Setter 사용
1 2 3 | QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.bean(ItemDTO.class, item.name.as("username"), item.price)); | cs |
ㅇ 필드 직접 접근
1 2 3 | QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.fields(ItemDTO.class, item.name.as("username"), item.price)); | cs |
ㅇ 생성자 사용
1 2 3 4 | QItem item = QItem.item; List<ItemDTO> result = query.from(item).list( Projections.constructor(ItemDTO.class, item.name, item.price) ); | cs |
||| DISTINCT
1 | query.distinct().from(item) ... | cs |
|| 수정, 삭제 배치 쿼리
JPQL 배치 쿼리와 같이 영속성 컨텍스트를 무시하고 데이터베이스를 직접 쿼리
ㅇ 수정 배치 쿼리
- 수정된 엔티티의 개수 리턴
1 2 3 4 5 | QItem item = QItem.item; JPAUpdateClause updateClause = new JPAUpdateClause(em, item); long count = updateClause.where(item.name.eq("test 입니다?")) .set(item.price, item.price.add(100)) .excute(); | cs |
ㅇ 삭제 배치 쿼리
1 2 3 4 | QItem item = QItem.item; JPADeleteClause deleteClause = new JPADeleteClause(em, item); long count = deleteClause.where(item.name.eq("test 입니다?")) .excute(); | cs |
|| 동적 쿼리
com.querydsl.core.BooleanBuilder 를 사용하여 특정 조건에 따른 동적 쿼리 생성
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | // 상품 이름과 가격 유무에 따른 동적 쿼리 SearchParam param = new SearchParam(); param.setName("test 입니다?"); 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); | cs |
|| 메소드 위임
메소드 위임 기능을 사용하면 쿼리 타입에 검색 조건 정의 가능
ㅇ 검색 조건 정의
1. 정적 메소드 만들기
2. @QueryDelegate Annotaion 속성으로 이 기능을 적용할 엔티티 지정
3. 첫 번째 파라미터에는 대상 엔티티의 쿼리 타입을 지정
1 2 3 4 5 6 7 | public class ItemExpression { @QueryDelegate(Item.class) public static BooleanExpression isExpensive(QItem, Integer price) { return item.price.gt(price); } } | cs |
ㅇ 메소드 위임 기능 사용
1 | query.from(item).where(item.isExpensive(30000)).list(item); | cs |
+ 필요 시 자바 기본 내장 타입에도 메소드 위임 기능 사용 가능
1 2 3 4 | @QueryDelegate(String.class) public static BooleanExpression isHello(StringPath stringPath) { return stringPath.startWith("Hello"); } | cs |
출처 : 자바 ORM 표준 JPA 프로그래밍
'Books' 카테고리의 다른 글
[JPA] 벌크 연산이란? (3) | 2020.12.23 |
---|---|
[JPA] 네이티브 SQL 정리 (0) | 2020.12.23 |
[JPA] JPQL Query 정리 (0) | 2020.12.22 |
[JPA] 고급 매핑(상속관계, 복합키, 식별/비식별, 조인 테이블) (0) | 2020.12.21 |
[JPA] 다양한 연관관계 매핑 (0) | 2020.12.21 |