<목차>
앞선 포스팅에서, QueryDSL 을 사용하기 위한 설정 방법에 대해 알아보았다.
이번 포스팅에서는, 간단한 테스트 코드를 통해 QueryDSL을 사용하는 방법에 대해 알아보자.
1. JPAQuery / JPAQueryFactory
2. QuerydslPredicateExecutor
3. QuerydslRepositorySupport
QueryDSL 설정 방법은 아래 포스팅을 참고
[Spring Boot] JPA - 6. QueryDSL 설정
아래 환경에서 QueryDSL을 사용하기 위한 설정 방법에 대해 알아보자.IntelliJSpringboot 2.7.18Maven1. QueryDSL ?2. QueryDSL 설정 방법 (QDomain) [QueryDSL ?]JPQL의 경우, 쿼리 메소드 외에도 @Query 어노테이션을 사용
jh4dev.tistory.com
[JPAQuery / JPAQueryFactory]
<JPAQuery>
JPAQuer 는 EntityManager 를 활용하여 생성한다.
@Autowired
EntityManager entityManager;
@Test
void queryDslTest01(){
JPAQuery<Product> query = new JPAQuery<Product>(entityManager);
QProduct qProduct = QProduct.product;
List<Product> productList = query.from(qProduct).where(qProduct.name.eq("펜")).orderBy(qProduct.price.asc()).fetch();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("==============================");
for(Product product : productList) {
stringBuilder.append("---------------------------- \n")
.append("Product Number : ").append(product.getNumber()).append("\n")
.append("Product Name : ").append(product.getName()).append("\n")
.append("Product Price : ").append(product.getPrice()).append("\n")
.append("Product Stock : ").append(product.getStock()).append("\n")
.append("---------------------------- \n");
}
log.info(stringBuilder.toString());
}
- EntityManager 를 통해 JPAQuery 객체를 생성해야 하므로, EntityManager 의존성을 주입해줬다.
- APT 설정을 통해 생성된 Product Entity 의 QClass 인 QProduct 를 사용한다.
- query.from().where().... 과 같이 builder 형식으로 쿼리를 작성한다.
- List 타입으로 리턴받기 위해 fetch() 메서드를 사용하였다.
이 외에도, 아래 메서드들이 있다.
- T fetchOne() : 단 건 리턴
- T fetchFirst() : 조회 결과 중 1건 리턴 (orderby)
- Long fetchCount() : 조회 결과의 Row 수 리턴
- QueryResult<T> fetchResults() : 조회 결과 리스트와 개수를 포함한 QueryResult 리턴
<JPAQueryFactory>
JPAQuery 는 from 절부터 시작했다면, JPAQueryFactory는 select 절부터 작성 가능하다. (selectFrom())
만약, 일부 컬럼만 조회하겠다면, select() 와 from() 을 나누어 작성한다. (단, 조회 결과를 Tuple 객체로 리턴)
@Autowired
EntityManager entityManager;
@Test
void queryDslTest03() {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QProduct qProduct = QProduct.product;
// Single select
List<String> productList = jpaQueryFactory
.select(qProduct.name)
.from(qProduct)
.where(qProduct.name.eq("펜"))
.orderBy(qProduct.price.asc())
.fetch();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\n==============================\n");
for(String product : productList) {
stringBuilder.append("---------------------------- \n")
.append("Product Name : ").append(product).append("\n")
.append("---------------------------- \n");
}
log.info(stringBuilder.toString());
// List Select
List<Tuple> tupleList = jpaQueryFactory
.select(qProduct.name, qProduct.price)
.from(qProduct)
.where(qProduct.name.eq("펜"))
.orderBy(qProduct.price.asc())
.fetch();
stringBuilder.setLength(0);
stringBuilder.append("\n==============================\n");
for(Tuple tuple : tupleList) {
stringBuilder.append("---------------------------- \n")
.append("Product Name : ").append(tuple.get(qProduct.name)).append("\n")
.append("Product Price : ").append(tuple.get(qProduct.price)).append("\n")
.append("---------------------------- \n");
}
log.info(stringBuilder.toString());
}
매번 EntityManager 를 주입받아 JPAQueryFactory를 생성하는 것은 매우 번거롭기 때문에, EntityManager로 생성한 JPAQueryFactory 객체를 빈으로 등록하여 사용하도록 하자.
** JPAQueryFactory Bean 등록 **
@Configuration
public class QueryDSLConfig {
@PersistenceContext
EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
** Bean으로 등록한 JPAQueryFactory 를 주입받아 작성
@Autowired
JPAQueryFactory jpaQueryFactory;
@Test
void queryDslTest04() {
QProduct qProduct = QProduct.product;
List<String> productList = jpaQueryFactory
.select(qProduct.name)
.from(qProduct)
.where(qProduct.name.eq("펜"))
.orderBy(qProduct.price.asc())
.fetch();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("\n==============================\n");
for(String product : productList) {
stringBuilder.append("---------------------------- \n")
.append("Product Name : ").append(product).append("\n")
.append("---------------------------- \n");
}
log.info(stringBuilder.toString());
}
[QuerydslPredicateExecutor]
QuerydslPredicateExecutor 는 Spring Data JPA 프레임워크에서 QueryDSL을 더 펴한게 사용할 수 있도록 제공하는 인터페이스이다.
JpaRepository 와 함께 리포지토리에서 QueryDSL 을 사용할 수 있도록 제공되는 인터페이스이며, 아래와 같이 리포지토리를 생성하여 사용할 수 있다.
** Repository 생성
public interface QProductRepository
extends JpaRepository<Product, Long>, QuerydslPredicateExecutor<Product> {
}
그렇다면, QuerydslPredicateExecutor 인터페이스에서 제공되는 메서드를 살펴보자.
<QuerydslPredicateExecutor Interface>
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.data.querydsl;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import java.util.Optional;
import java.util.function.Function;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.query.FluentQuery;
public interface QuerydslPredicateExecutor<T> {
Optional<T> findOne(Predicate predicate);
Iterable<T> findAll(Predicate predicate);
Iterable<T> findAll(Predicate predicate, Sort sort);
Iterable<T> findAll(Predicate predicate, OrderSpecifier<?>... orders);
Iterable<T> findAll(OrderSpecifier<?>... orders);
Page<T> findAll(Predicate predicate, Pageable pageable);
long count(Predicate predicate);
boolean exists(Predicate predicate);
<S extends T, R> R findBy(Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction);
}
- QuerydslPredicateExecutor 의 메서드들을 살펴보면, 위와 같이 Predicate 타입을 파라미터로 받고 있다.
- Predicate 는 표현식을 작성할 수 있도록 QueryDSL 에서 제공하는 인터페이스이며, 간단하게 표현식으로 정의하는 쿼리로 생각하면 된다.
- 다만, QuerydslPredicateExecutor 의 경우, join 이나 fetch 기능은 사용할 수 없다는 단점이 있다.
<Predicat 인터페이스를 활용한 QueryDSL 사용 방법>
@Autowired
QProductRepository qProductRepository;
@Test
void predicateFindOneTest() {
Predicate predicate = QProduct.product.name.containsIgnoreCase("펜")
.and(QProduct.product.price.between(1000, 2500));
Optional<Product> foundProduct = qProductRepository.findOne(predicate);
if(foundProduct.isPresent()) {
Product product = foundProduct.get();
System.out.println(product);
}
}
QuerydslPredicateExecutor 공식 문서
QuerydslPredicateExecutor (Spring Data Core 3.3.2 API)
All Known Subinterfaces: ListQuerydslPredicateExecutor public interface QuerydslPredicateExecutor Interface to allow execution of QueryDsl Predicate instances. Author: Oliver Gierke, Thomas Darimont, Christoph Strobl, Mark Paluch See Also: Method Summary A
docs.spring.io
[QuerydslRepositorySupport]
QuerydslRepositorySupport 클래스는 이름에서 볼 수 있듯이, Repository 를 서포트하는 기능을 위한 클래스이다.
주로 커스텀 리포지토리 구현체를 작성할 때 사용되며, Spring Data JPA 의 리포지토리 인터페이스에 추가적인 메서드를 구현해야하는 상황에 QuerydslRepositorySupport 활용할 수 있다.
커스텀 리포지토리를 구현하는 방식은 다음과 같다. (다양한 방식이 있으며, 일반적인 방식에 대한 설명입니다.)
이해를 쉽게 하기 위하여, Product 라는 Entity 가 존재한다고 가정한다.
* 클래스
* 인터페이스
- JpaRepository 를 상속받는 ProductRepository 인터페이스 생성 (기본 리포지토리)
- JpaRepository 를 상속받지 않는 ProductRepositoryCustom 인터페이스 생성 및 메서드 정의
- ProductRepository 에서 ProductRepositoryCustom 상속
- ProductRepositoryCustom 의 구현체 ProductRepositoryCustomImpl 생성 및 작성
- ProductRepositoryCustomImpl 에서 QueryDSL 을 사용하기 위해 QuerydslRepositorySupport 상속
이와 같이 구성함으로써, 서비스나 DAO 레이어에서는 ProductRepository 라는 엔티티명 기반의 리포지토리를 사용할 수 있으며, JPA 기본 메서드와 더불어 QueryDSL 의 기능 또한 사용할 수 있다.
위 구조로 QuerydslRepositorySupport 를 사용하기 위해, 각 인터페이스와 소스는 다음과 같이 구성한다.
<ProductRepositoryCustom>
public interface ProductRepositoryCustom {
//Custom 메서드 작성
List<Product> findByName(String name);
}
<ProductRepositoryCustomImpl>
@Component
public class ProductRepositoryCustomImpl
extends QuerydslRepositorySupport
implements ProductRepositoryCustom{
//QuerydslRepositorySupport 를 상속받는 경우,
//부모 클래스로 도메인 클래스 전달
public ProductRepositoryCustomImpl() {
super(Product.class);
}
@Override
public List<Product> findByName(String name){
QProduct product = QProduct.product;
List<Product> productList = from(product)
.where(product.name.eq(name))
.select(product)
.fetch();
return productList;
}
}
- QuerydslRepositorySupport 를 상속 받는 경우,
생성자를 통해 도메인 클래스를 부모 클래스(QuerydslRepositorySupport) 로 전달해야 한다. - findByName() 메서드를 살펴보자
- QuerydslRepositorySupport 의 기능을 사용하기 위해서는, QClass(QProduct) 를 사용한다.
<ProductRepository>
@Repository
public interface ProductRepository
extends JpaRepository<Product, Long>
, ProductRepositoryCustom {
}
<테스트 코드>
@SpringBootTest
public class ProductRepositoryTest extends RepositoryTestBase{
//위 과정을 걸쳐 생성한 productRepository를 주입받는다.
@Autowired
ProductRepository productRepository;
@Test
void findByNameTest() {
List<Product> productList = productRepository.findByName("펜");
for(Product product : productList) {
System.out.println(product.toString());
}
}
}
QuerydslRepositorySupport 공식 문서
QuerydslRepositorySupport (Spring Data JPA Parent 3.3.2 API)
java.lang.Object org.springframework.data.jpa.repository.support.QuerydslRepositorySupport Base class for implementing repositories using Querydsl library. Author: Oliver Gierke, Mark Paluch Constructor Summary Constructors Method Summary All MethodsInstan
docs.spring.io
[Github]
https://github.com/jh4dev/spring-boot-study/tree/main/advjpa
spring-boot-study/advjpa at main · jh4dev/spring-boot-study
스프링 부트 스터디. Contribute to jh4dev/spring-boot-study development by creating an account on GitHub.
github.com
[참고 도서]

<스프링 부트 핵심 가이드>
저자 : 장정우
출판사 : 위키북스
'Spring & Spring boot' 카테고리의 다른 글
[Spring Boot] Validation - 1. @Valid 어노테이션 사용방법 (0) | 2024.08.14 |
---|---|
[Spring Boot] JPA - 8. 연관관계 매핑 (Relation) (0) | 2024.08.13 |
[Spring Boot] JPA - 6. QueryDSL 설정 (0) | 2024.08.11 |
[Spring Boot] JPA - 5. JPQL Query Method 쿼리 메서드 (0) | 2024.08.10 |
[Springboot] 테스트 코드 - 8 - Repository Unit Test 리포지토리 단위 테스트 (0) | 2024.08.08 |