FireDrago
[QueryDSL] QueryDSL 사용법 본문
스프링 JPA , JPA 모두 동적 쿼리 작성에 어려움이 있었다. 이를 보완하기 위해 Querydsl을 함께 사용하는 경우가 많다.
Querydsl은 자바 코드로 쿼리를 편리하게 작성할 수 있게해주고, 컴파일 단계에서 쿼리작성 오류를 잡아낸다.
당연히 조인, 집계 등의 다양한 쿼리 기능도 지원한다. 스프링에서 Querydsl을 사용하는 방법을 알아보자
Querydsl 설정하기
dependencies {
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}
clean {
delete file('src/main/generated')
}
build.gradle에 다음과 같이 설정한다.


gradle 로 빌드와 테스트를 실행하도록 설정한다음
gradle > Tasks > clean 실행한다.
gradle > Tasks > compileJava 실행한다.
두번째 이미지 경로에 Qitem 객체가 생성되면 Querydsl을 사용할수 있다.
Querydsl 적용하기
@Repository
@Transactional
public class JpaItemRepositoryV3 implements ItemRepository {
/*JPA 의존성 등록했다면 스프링이 자동으로 등록*/
private final EntityManager em;
//Querydsl 사용하기위해 필요
private final JPAQueryFactory query;
public JpaItemRepositoryV3(EntityManager em) {
this.em = em;
// JPA 쿼리인 JPQL을 만들기 때문에 EntityManager 가 필요
this.query = new JPAQueryFactory(em);
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
String itemName = cond.getItemName();
Integer maxPrice = cond.getMaxPrice();
// 자바 코드를 통해 JPQL을 생성할 수 있다.
return query
.select(item)
.from(item)
//where(A,B) 이렇게 넣으면 AND 조건으로 처리
.where(likeItemName(itemName), maxPrice(maxPrice))
.fetch();
}
//BooleanExpression 반환하여 조건 동적 생성 (재사용 가능)
private BooleanExpression likeItemName(String itemName) {
if (StringUtils.hasText(itemName)) {
return item.itemName.like("%" + itemName + "%");
}
return null;
}
private BooleanExpression maxPrice(Integer maxPrice) {
if (maxPrice != null) {
return item.price.loe(maxPrice);
}
return null;
}
}
findAll 메서드에서 Querydsl 을 사용하여 동적쿼리를 생성하고있다. 기본 기능은 생략했다.
- 쿼리 문장에 오타가 있어도 컴파일 시점에 오류를 막을 수 있다.
- 메서드 추출을 통해서 코드를 재사용할 수 있다.
예를 들어서 여기서 만든 likeItemName(itemName) , maxPrice(maxPrice) 메서드를 다른 쿼리에서 사용할 수 있다
스프링 데이터 JPA + Querydsl 함께 사용
@Service
@RequiredArgsConstructor
//JPA는 @Transactional 필수
@Transactional
public class ItemServiceV2 implements ItemService{
//간단한 CRUD는 스프링 데이터 JPA 사용
private final ItemRepositoryV2 itemRepositoryV2;
//동적쿼리를 위한 Querydsl 사용
private final ItemQueryRepositoryV2 itemQueryRepositoryV2;
@Override
public Item save(Item item) {
itemRepositoryV2.save(item);
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
Item item = findById(itemId).orElseThrow();
item.setItemName(updateParam.getItemName());
item.setPrice(updateParam.getPrice());
item.setQuantity(updateParam.getQuantity());
}
@Override
public Optional<Item> findById(Long id) {
return itemRepositoryV2.findById(id);
}
@Override
public List<Item> findItems(ItemSearchCond itemSearch) {
return itemQueryRepositoryV2.findAll(itemSearch);
}
}
서비스 객체는 복잡한 동적 쿼리가 필요할때는 ItemQueryRepostiroyV2 참조
간단한 CRUD 기능은 ItemRepositoryV2를 통해 스프링데이터 JPA 사용
그런데 이 서비스 객체는 인터페이스가 아니라 구체적인 구현객체에 의존한다. (트레이드오프)
이렇게 되면 변경에 취약해지지만, 간단하고 빠르게 구현할 수 있다.
추상화 역시 비용이 들고, 변화가능성이 거의 없을때는 오버 엔지니어링이 될 수 도 있다.
개발자는 상황을 잘 판단하고 현재 상황에 맞는 설계전략을 선택할 수 있어야 한다.
반드시 추상화가 답이 아니라는 것이다. 변경가능성이 없고 간단한 경우 트레이드오프를 고려하자
//스프링 데이터 JPA 는 JpaRepository<도메인객체, pk타입> 구현만으로 기본기능 사용가능
//스프링이 구현체를 자동으로 생성한다.
public interface ItemRepositoryV2 extends JpaRepository<Item, Long> {
}
//동적 쿼리를 담당하는 Querydsl
@Repository
public class ItemQueryRepositoryV2 {
private final JPAQueryFactory query;
public ItemQueryRepositoryV2(EntityManager em) {
this.query = new JPAQueryFactory(em);
}
public List<Item> findAll(ItemSearchCond cond) {
return query.select(item)
.from(item)
.where(maxPrice(cond.getMaxPrice()), likeItemName(cond.getItemName()))
.fetch();
}
private BooleanExpression likeItemName(String itemName) {
if (StringUtils.hasText(itemName)) {
return item.itemName.like("%" + itemName + "%");
}
return null;
}
private BooleanExpression maxPrice(Integer maxPrice) {
if (maxPrice != null) {
return item.price.loe(maxPrice);
}
return null;
}
}
'프로그래밍 > QueryDSL' 카테고리의 다른 글
| [QueryDSL] 기본 문법 1 (0) | 2024.05.01 |
|---|
