@Query 어노테이션에도 단점이 있다.
@Query 어노테이션 안에 JPQL 문법으로 문자열을 입력하기 때문에 잘못 입력하면 컴파일 시점에 에러를 발견할 수 없다...
이를 보완하기 위한 방법이 Querydsl
정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해주는 프레임워크
--> 쿼리 생성을 자동화하여 자바 코드로 작성할 수 있다!
Querydsl을 사용하기 위해서는 추가 설정을 해주어야한다.
pom.xml 파일에 의존성을 추가해주자.
# QueryDSL JPA 라이브러리
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-jpa</artifactId>
<version>5.0.0</version>
</dependency>
# 쿼리타입(Q)를 생성할 때 사용하는 라이브러리
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>5.0.0</version>
</dependency>
Qdomain이라는 자바 코드를 생성해주는 플러그인도 추가해주자.
엔티티를 기반으로 접두사로 Q가 붙는 클래스들을 자동으로 생성해주는 플러그인.
예를 들어, Item 테이블의 경우, QItem.java 클래스가 자동으로 생성된다.
Item.java @Entity를 가지고, QItem.java라는 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>
</configuration>
</execution>
</executions>
</plugin>
추가한 의존성을 받아오기 위해 인텔리제이 오른쪽에 [Maven]클릭 후 새로고침 버튼(?)을 클릭해줍니다. 앞으로도 pom.xml에 dependency를 추가하면 이 버튼을 눌러 해당 라이브러리를 다운로드 해주면 된다.
[maven compile]을 눌러주면 빌드가 되고, 빌드가 완료되면 target/generated-sources 폴더에 QItem 클래스가 생성된 것을 확인할 수 있다
인텔리제이에서 생성한 QDomain이 임포트 안 될 때가 있는데, 이때는 [File]-[Project Structure]-[Modules]에서 target폴더 아래의 generated-sources 폴더를 클릭하고 버튼을 클릭해 소스코드로 인식할 수 있게 처리한다.
Querydsl을 사용하기 위한 세팅은 완료.
JPQL에서 문자열로 작성하던 쿼리를 자바 소스를 이용해 동적으로 생성해보자.
ItemRepository.java
@SpringBootTest
@TestPropertySource(locations = "classpath:application-test.properties")
class ItemRepositoryTest {
@Autowired
ItemRepository itemRepository;
// 영속성 컨텍스트를 사용하기 위해 EntityManager 빈을 주입
@PersistenceContext
EntityManager em;
@Test
@DisplayName("Querydsl 조회 테스트1")
public void queryDslTest() {
this.createItemTest();
// 쿼리 동적 생성. 생성자의 파라미터로는 EntityManager 객체를 넣어준다.
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QItem qItem = QItem.item;
JPAQuery<Item> query = queryFactory.selectFrom(qItem)
.where(qItem.itemSellStatus.eq(ItemSellStatus.SELL))
.where(qItem.itemDetail.like("%" + "테스트 상품 상세 설명" + "%"))
.orderBy(qItem.price.desc());
List<Item> itemList = query.fetch();
for (Item item : itemList) {
System.out.println(item.toString());
}
}
}
메소드 | 기능 |
---|---|
List<T> fetch() | 조회 결과 리스트 반환 |
T fetchOne | 조회 대상이 1건인 경우 제네릭으로 지정한 타입 반환 |
T fetchFirst() | 조회 대상 중 1건만 반환 |
Long fetchCount() | 조회한 대상 개수 반환 |
QueryResult<T> fetchResults() | 조회한 리스트 전체 개수를 포함한 QueryResults 반환 |
ItemRepository.java
// QuerydslPredicateExecutor 인터페이스 상속 추가
public interface ItemRepository extends JpaRepository<Item, Long>, QuerydslPredicateExecutor<Item> {
....
}
메소드 | 기능 |
---|---|
long count(Predicate) | 조건에 맞는 데이터의 총 개수 반환 |
boolean exitsts(Predicate) | 조건에 맞는 데이터 존재 여부 반환 |
Iterable findAll(Predicate) | 조건에 맞는 모든 데이터 반환 |
Page<T> findAll(Predicate, Pageable) | 조건에 맞는 페이지 데이터 반환 |
Iterable findAll(Predicate, Sort) | 조건에 맞는 정렬된 데이터 반환 |
T findOne(Predicate) | 조건에 맞는 데이터 1개 반환 |
public void createItemList2(){
for(int i=1;i<=5;i++){
Item item = new Item();
item.setItemNm("테스트 상품" + i);
item.setPrice(10000 + i);
item.setItemDetail("테스트 상품 상세 설명" + i);
item.setItemSellStatus(ItemSellStatus.SELL);
item.setStockNumber(100);
item.setRegTime(LocalDateTime.now());
item.setUpdateTime(LocalDateTime.now());
itemRepository.save(item);
}
for(int i=6;i<=10;i++){
Item item = new Item();
item.setItemNm("테스트 상품" + i);
item.setPrice(10000 + i);
item.setItemDetail("테스트 상품 상세 설명" + i);
item.setItemSellStatus(ItemSellStatus.SOLD_OUT);
item.setStockNumber(0);
item.setRegTime(LocalDateTime.now());
item.setUpdateTime(LocalDateTime.now());
itemRepository.save(item);
}
}
@Test
@DisplayName("상품 Querydsl 조회 테스트 2")
public void queryDslTest2(){
this.createItemList2();
// 쿼리에 들어갈 조건을 만들어 주는 빌더
BooleanBuilder booleanBuilder = new BooleanBuilder();
QItem item = QItem.item;
String itemDetail = "테스트 상품 상세 설명";
int price = 10003;
String itemSellStat = "SELL";
booleanBuilder.and(item.itemDetail.like("%" + itemDetail + "%"));
booleanBuilder.and(item.price.gt(price));
System.out.println(ItemSellStatus.SELL);
// 상품 판매 상태가 SELL일 때민 booleanBuilder에 판매 상태 조건을 동적으로 추가
if(StringUtils.equals(itemSellStat, ItemSellStatus.SELL)){
booleanBuilder.and(item.itemSellStatus.eq(ItemSellStatus.SELL));
}
// 페이지네이션 PageRequest.of(조회할 페이지 번호, 한 페이지당 조회할 데이터 개수)
Pageable pageable = PageRequest.of(0, 5);
Page<Item> itemPagingResult = itemRepository.findAll(booleanBuilder, pageable);
System.out.println("total elements : " + itemPagingResult. getTotalElements ());
List<Item> resultItemList = itemPagingResult.getContent();
for(Item resultItem: resultItemList){
System.out.println(resultItem.toString());
}
}
테스트 코드를 실행하면 자바 코드에서 지정한 조건문이 정상적으로 추가되어있다!