FireDrago

[QueryDSL] QueryDSL 사용법 본문

프로그래밍/QueryDSL

[QueryDSL] QueryDSL 사용법

화이용 2024. 3. 12. 13:26

스프링 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