BackEnd/Database

QueryDSL

연향동큰손 2025. 2. 10. 17:15

 

 

QueryDSL 이란 하이버네이트 쿼리 언어의 쿼리를 타입에 안전하게 생성 및 관리해주는 프레임워크이다.

 

QueryDSL 장점

  • 기존의 MyBatis, JPQL 등의 문자열 형태로 쿼리문을 작성했을때는 쿼리문의 오류를 발견하는게 힘들지만 QueryDSL을 사용하면 자바 코드로 쿼리를 작성할 수 있어서 컴파일 오류를 통해 잘못된 쿼리가 실행되는 것을 방지할 수 있다.
  • 동적 쿼리를 매우 깔끔하게 사용할 수 있다.

 

 

QueryDSL 설정

 

<build.gradle>

 

스프링 부트 2.X 설정

// 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"

 

스프링 부트 3.X 설정

// 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"

 

 

//Querydsl 추가, 자동 생성된 Q클래스 gradle clean으로 제거
clean {
    delete file('src/main/generated')
}

 

 

Gradle -> Tasks -> build -> clean 실행

Gradle -> Tasks -> other -> compileJava 실행

 

 

QItem 클래스가 생성된 것을 확인하면 끝!

 

 

QueryDSL 사용 예시

 

 

package hello.itemservice.repository.jpa;

import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import hello.itemservice.domain.Item;
import hello.itemservice.domain.QItem;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;

import static hello.itemservice.domain.QItem.item;

@Repository
@Transactional
public class JpaItemRepositoryV3 implements ItemRepository {

    private final EntityManager em;
    private final JPAQueryFactory query;

    public JpaItemRepositoryV3(EntityManager em) {
        this.em = em;
        this.query = new JPAQueryFactory(em);
    }

    @Override
    public Item save(Item item) {
        em.persist(item);
        return item;
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        Item findItem = em.find(Item.class, itemId);
        findItem.setItemName(updateParam.getItemName());
        findItem.setPrice(updateParam.getPrice());
        findItem.setQuantity(updateParam.getQuantity());
    }

    @Override
    public Optional<Item> findById(Long id) {
        Item item = em.find(Item.class, id);
        return Optional.ofNullable(item);
    }
    public List<Item> findAllOld(ItemSearchCond itemSearch) {
        String itemName = itemSearch.getItemName();
        Integer maxPrice = itemSearch.getMaxPrice();
        QItem item = QItem.item;
        BooleanBuilder builder = new BooleanBuilder();
        if (StringUtils.hasText(itemName)) {
            builder.and(item.itemName.like("%" + itemName + "%"));
        }
        if (maxPrice != null) {
            builder.and(item.price.loe(maxPrice));
        }
        List<Item> result = query
                .select(item)
                .from(item)
                .where(builder)
                .fetch();
        return result;
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();


        List<Item> result = query
                .select(item)
                .from(item)
                .where(likeItemName(itemName), maxPrice(maxPrice))
                .fetch();
        return result;
    }
    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을 사용하기 위해서는 JPAQueryFactory가 필요하다.

 

<JPQL을 이용하여 구현한 findAll()메서드>

@Override
public List<Item> findAll(ItemSearchCond cond) {
    String jpql = "select i from Item i";
    Integer maxPrice = cond.getMaxPrice();
    String itemName = cond.getItemName();
    if (StringUtils.hasText(itemName) || maxPrice != null) {
        jpql += " where";
    }
    boolean andFlag = false;
    if (StringUtils.hasText(itemName)) {
        jpql
                += " i.itemName like concat('%',:itemName,'%')";
        andFlag = true;
    }
    if (maxPrice != null) {
        if (andFlag) {
            jpql += " and";
        }
        jpql += " i.price <= :maxPrice";
    }

    log.info("jpql={}", jpql);
    TypedQuery<Item> query = em.createQuery(jpql, Item.class);
    if (StringUtils.hasText(itemName)) {
        query.setParameter("itemName", itemName);
    }
    if (maxPrice != null) {
        query.setParameter("maxPrice", maxPrice);
    }
    return query.getResultList();
}

 

 

두 findAll 을 비교해보면 QueryDSL을 이용하여 동적쿼리를 구현하면, 자바 코드로 쿼리를 작성하기 때문에 코드의 가독성이 훨씬 높아지는 것을 확인할 수 있었고,

 

컴파일 오류를 이용하여 더욱 정확한 쿼리를 작성할 수 있었다.

'BackEnd > Database' 카테고리의 다른 글

스프링 트랜잭션 전파  (0) 2025.02.14
Spring 트랜잭션[@Transactional]  (0) 2025.02.12
Spring Data JPA  (0) 2025.02.10
MyBatis  (0) 2025.02.08
DB Test[@Transactional, 임베디드 모드 DB]  (0) 2025.02.07