@Query 어노테이션 사용하기
메서드의 이름만으로 쿼리 메서드를 생성할 수 있다.
@Query 어노테이션을 사용해 직접 JPQL을 작성할 수 있는데 알아보겠습니다.
@Query 어노테이션
- JPQL을 사용하면 JPA 구현체에서 자동으로 쿼리 문장을 해석하고 실행하게 된다.
- 만약 데이터베이스를 다른 데이터베이스로 변경할 일이 없다면 직접 해당 데이터베이스에 특화된 SQL을 작성할 수 있다.
- 주로 튜닝된 쿼리를 사용하고자 할 때에 직접 SQL을 작성한다.
@Query 어노테이션을 사용하는 메서드
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query(" select p from Product as p where p.name = ?1")
List<Product> findByName(String name);
}
- @Query 어노테이션을 사용해 JPQL 형식의 쿼리문을 작성한다.
- From 뒤에 엔티티 타입을 지정하고, 별칭을 생성한다. (As 는 생략 가능하다)
- Where 문에서는 SQL과 마찬가지로 조건을 지정한다.
- 조건문에서 사용한 '?1' 은 파라미터를 전달받기 위한 인자에 해당한다.
- 1 은 첫 번째 파라미터를 의미한다.
@Query 어노테이션과 @Param 어노테이션을 사용한 메서드
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query(" select p from Product as p where p.name = ?1")
List<Product> findByNameParam(@Param("name") String name);
}
- 파라미터의 순서가 바뀌면 오류가 발생할 가능성이 있으므로 @Param 어노테이션을 사용하는 것이 좋다.
- 파라미터를 바인딩하는 방식으로 메서드를 구현하면 코드의 가독성이 높아지고 유지보수가 수월해진다.
특정 컬럼만 추출하는 쿼리
@Repository
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query(" select p.name, p.price, p.stock from Product p where p.name = :name ")
List<Product> findByNameParam2(@Param("name") String name);
}
- Select 에 가져오고자 하는 컬럼을 지정하면 된다.
- Object 배열의 리스트 형태로 리턴 타입을 지정해야한다.
QueryDSL 적용하기
- 정적 타입을 이용해 SQL과 같은 쿼리를 생성할 수 있도록 지원하는 프레임워크이다.
- 문자열이나 XML 파일을 통해 쿼리를 작성하는 대신 QueryDSL이 제공하는 플루언트(Fluent) API를 활용하여 쿼리를 생성할 수 있다.
QueryDSL의 장점
- IDE가 제공하는 코드 자동 완성 기능을 사용할 수 있다.
- 문법적으로 잘못된 쿼리를 허용하지 않는다. 따라서 정상적인 QueryDSL은 문법 오류를 발생시키지 않는다.
- 고정된 SQL 쿼리를 작성하지 않기 때문에 동적으로 쿼리를 생성할 수 있다.
- 코드를 작성하므로 가독성 및 생산성이 향상된다.
- 도메인 타입과 프로퍼티를 안전하게 참조할 수 있다.
QueryDSL 의존성 추가(pom.xml)
<!-- QueryDSL 추가-->
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
</dependency>
QueryDSL 플러그인 추가(pom.xml)
<!-- QueryDSL 추가-->
<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>
<options>
<querydsl.entityAccessors>true</querydsl.entityAccessors>
</options>
</configuration>
</execution>
</executions>
</plugin>
QueryDSL 확인
- Maven -> Lifecycle(수명 주기) -> [complie] 클릭하여 빌드 작업 진행
- plugin에 지정했던 generated-source 경로에 Q도메인 클래스 생성
QueryDSL 설정
- QueryDSL은 지금까지 작성했던 엔티티 클래스와 Q도메인(Qdomain)이라는 쿼리 타입의 클래스를 자체적으로 생성하여 메타데이터로 사용하는데, 이를 SQL과 같은 쿼리를 생성해서 제공한다.
- Q도메인이 클래스가 제대로 생성되지 않으면 [프로젝트 폴더 오른쪽 마우스 클릭] -> [Maven] -> [Generate Sources and Update Folders]를 선택한다.
QueryDSL IDE 설정
- [File] -> [Project Structure] -> [Module] 탭을 클릭한다.
- generated-sources 폴더를 눌러 소스파일을 인식할 수 있게 [Sources] 을 클릭한다.
QueryDSL 코드 작성
JPAQuery를 활용한 QueryDSL 테스트 코드
@SpringBootTest
class ProductRepositoryTest {
@PersistenceContext
EntityManager entityManager;
@Test
void queryDslTest() {
JPAQuery<Product> query = new JPAQuery<>(entityManager);
QProduct qProduct = QProduct.product;
List<Product> productList = query.from(qProduct)
.where(qProduct.name.eq("pen"))
.orderBy(qProduct.price.asc())
.fetch();
for (Product product : productList) {
System.out.println("====================");
System.out.println();
System.out.println("Product Number : " + product.getNumber());
System.out.println("Product Name : " + product.getName());
System.out.println("Product Price : " + product.getPrice());
System.out.println("Product Stock : " + product.getStock());
System.out.println();
System.out.println("====================");
}
}
}
- QueryDSL에 의해 생성된 Q도메인 클래스를 활용하는 코드이다.
- QueryDSL을 사용하기 위해서는 JPAQuery 객체를 사용한다.
- JPAQuery는 엔티티 매니저(EntityManager)를 활용하여 생성한다.
- 빌더 메서드에서 확인할 수 있듯이, SQL 쿼리에서 사용되는 키워드로 메서드가 구성되어있다.
- List 타입으로 값을 리턴받기 위해서는 fetch() 메서드를 사용한다.
- ver 4.0.1 이전인 경우 list() 메서드를 사용해야한다.
JPAQuery 메서드 종류
- List<T> fetch() : 조회 결과를 리스트로 반환한다.
- T fetchOne : 단 건의 조회 결과를 반환한다.
- T fetchFirst() : 여러 건의 조회 결과 중 1건을 반환한다. 내부 로직 상 ' .limit(1).fetchOne()' 으로 구성되어있다.
- Long fetchCount() : 조회 결과의 개수를 반환한다.
- QueryResult<T> fetchResults() : 조회 결과 리스트와 개수를 포함한 QueryResults 를 반환한다.
JPAQueryFactory를 활용한 QueryDSL 테스트 코드
- JPAQuery를 사용했을 떄와 달리 JPAQueryFactory에서는 select 절부터 작성이 가능하다.
- 일부 컬럼 조회를 할 경우 selectFrom( ) 이 아닌 select( ) 와 from( ) 메서드를 구분해서 사용하면 된다.
@SpringBootTest
class ProductRepositoryTest {
@PersistenceContext
EntityManager entityManager;
@Test
void queryDslTest2() {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QProduct qProduct = QProduct.product;
List<Product> productList = jpaQueryFactory.selectFrom(qProduct)
.where(qProduct.name.eq("pen"))
.orderBy(qProduct.price.asc())
.fetch();
for (Product product : productList) {
System.out.println("====================");
System.out.println();
System.out.println("Product Number : " + product.getNumber());
System.out.println("Product Name : " + product.getName());
System.out.println("Product Price : " + product.getPrice());
System.out.println("Product Stock : " + product.getStock());
System.out.println();
System.out.println("====================");
}
}
}
JPAQueryFactory의 select() 메서드 테스트
- 조회 대상이 여러 개일 경우에는 쉼표(,)로 구분하여 작성하면 된다.
- List<Tuple> 타입으로 지정한다.
@SpringBootTest
class ProductRepositoryTest {
@PersistenceContext
EntityManager entityManager;
@Test
void queryDslTest3() {
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(entityManager);
QProduct qProduct = QProduct.product;
List<String> productList = jpaQueryFactory
.select(qProduct.name)
.from(qProduct)
.where(qProduct.name.eq("pen"))
.orderBy(qProduct.price.asc())
.fetch();
for (String product : productList) {
System.out.println("====================");
System.out.println("Product Name : " + product);
System.out.println("====================");
}
List<Tuple> tupleList = jpaQueryFactory
.select(qProduct.name, qProduct.price)
.from(qProduct)
.where(qProduct.name.eq("pen"))
.orderBy(qProduct.price.asc())
.fetch();
for (Tuple product : tupleList) {
System.out.println("====================");
System.out.println("Product Name : " + product.get(qProduct.name));
System.out.println("Product Price : " + product.get(qProduct.price));
System.out.println("====================");
}
}
}
QueryDSL Config 파일 생성
- JPAQueryFactory 객체를 @Bean 객체로 등록하면 초기화하지 않고 스프링 컨테이너에서 가져다 쓸 수 있다.
@Configuration
public class QueryDSLConfiguration {
@PersistenceContext
EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
JPAQueryFactory @Bean 파일 활용한 테스트
- JPAQueryFactory를 의존성 주입하여 쿼리를 작성한다.
@SpringBootTest
class ProductRepositoryTest {
@Autowired
JPAQueryFactory jpaQueryFactory;
@Test
void queryDslTest4() {
QProduct qProduct = QProduct.product;
List<String> productList = jpaQueryFactory
.select(qProduct.name)
.from(qProduct)
.where(qProduct.name.eq("pen"))
.orderBy(qProduct.price.asc())
.fetch();
for (String product : productList) {
System.out.println("====================");
System.out.println("Product Name : " + product);
System.out.println("====================");
}
}
}
QuerydslPredicateExecutor
QueryDSL을 더욱 편하게 사용할 수 있는 클래스를 활용해보겠습니다.
QuerydslPredicateExecutor
- JpaRepository와 함께 리포지토리에서 QueryDSL을 사용할 수 있게 인터페이스를 제공한다.
@Repository
public interface QProductRepository extends JpaRepository<Product, Long>,
QuerydslPredicateExecutor<Product>{
}
- 대부분 Predicate 타입으로 매개변수를 받는다.
- Predicate : 표현식을 작성할 수 있게 QueryDSL에서 제공하는 인터페이스이다.
QuerydslPredicateExecutor 테스트 코드
@SpringBootTest
class QProductRepositoryTest {
@Autowired
QProductRepository qProductRepository;
@Test
void queryDSLTest1() {
Predicate predicate = QProduct.product.name.containsIgnoreCase("pen")
.and(QProduct.product.price.between(1000, 2500));
Optional<Product> foundProduct = qProductRepository.findOne(predicate);
if (foundProduct.isPresent()) {
System.out.println(foundProduct.get().getNumber());
System.out.println(foundProduct.get().getName());
System.out.println(foundProduct.get().getPrice());
System.out.println(foundProduct.get().getStock());
}
}
}
QuerydslRepositorySupport 활용
QuerydslRepositorySupport 추상 클래스
- CustomRepository를 활용하여 리포지토리를 구현하는 방식이 보편적이다.
- QuerydslRepositorySupport 를 사용하기 위해서는 CustomRepository와 CustromImpl을 직접 구현해야한다.
QuerydslRepositoryCustomImpl 클래스
- QueryDSL을 사용하기 위해 QuerydslRepositorySupport 를 상속받고 인터페이스를 구현한다.
- QuerydslRepositorySupport 를 상속받으면 생성자를 통해 도메인 클래스를 부모 클래스에 전달해야한다.( super( ) )
- from( ) : 어떤 도메인에 접근할 것인지 지정하고, JPAQuery로 리턴한다.
@Component
public class ProductRepositoryCustomImpl extends QuerydslRepositorySupport
implements ProductRepositoryCustom{
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;
}
}
ProductRepository 설정
@Repository("productRepositorySupport")
public interface ProductRepositorySupport extends JpaRepository<Product, Long>
, ProductRepositoryCustom {
}
ProductRepository 메서드 테스트
@SpringBootTest
class ProductRepositorySupportTest {
@Autowired
ProductRepositorySupport productRepositorySupport;
@Test
void findByNameTest() {
List<Product> productList = productRepositorySupport.findByName("pen");
for (Product product : productList) {
System.out.println(product.getNumber());
System.out.println(product.getName());
System.out.println(product.getPrice());
System.out.println(product.getStock());
}
}
}
반응형
'Book > 스프링부트 핵심가이드' 카테고리의 다른 글
JPA Auditing 개념과 활용 방법 (0) | 2023.11.09 |
---|---|
정렬과 페이징 처리 방법과 예시 (1) | 2023.11.09 |
Spring Data JPA 쿼리 메서드 개념과 사용 방법 (0) | 2023.11.08 |
댓글