공부/Spring

데이터 접근 기술 - Querydsl

Stair 2025. 6. 9. 11:08
반응형

Querydsl 설정

우선 Querydsl을 설정해보자. 스프링 2.x대의 설정과 3.x대의 설정이 다르므로 잘 확인해보자.

 

2.x 버전 설정

//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
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')
}

clean을 추가해주자.

 

 

검증 - Q 타입 생성 확인 방법

Gradle일 시엔 Q 타입 생성 확인 방법은 다음과 같다.

1. Gradle -> Tasks -> build -> clean

2. Gradle -> Tasks -> other -> compileJava

 

위 순서를 지켜 준 후 패키지의

build -> generated -> sources -> annotationProcessor -> java/main 하위에

hello.itemservice.domain.QItem이 생성되어 있는지 확인한다.

 

 

Intellij에 IDEA옵션을 선택하면 src/main/generated에 파일이 생성되고, 필요한 경우 Q파일을 직접 삭제해야 한다.

gradle에 해당 스크립트를 추가하면 gradle clean 명령어를 실행할 때 src/main/generated의 파일도 함께 삭제해준다.

 

참고

Querydsl은 이렇게 설정하는 부분이 사용하면서 조금 귀찮은 부분인데, Intellij가 버전업 하거나 Querydsl의 Gradle 설정이 버전업 하면 적용 방법이 조금씩 달라지기도 한다. 그리고 본인의 환경에 따라서 잘 동작하지 않기도 한다. 공식 메뉴얼에 소개되어 있는 부분이 아니기 때문에, 설정에 수고로움이 있지만 querydsl gradle로 검색하면 본인 환경에 맞는 대안을 빠르게 찾을 수 있을 것이다.

 

 

 

Querydsl 적용

@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.setQuantity(updateParam.getQuantity());
        findItem.setPrice(updateParam.getPrice());
    }

    @Override
    public Optional<Item> findById(Long id) {
        Item item = em.find(Item.class, id);
        return Optional.ofNullable(item);
    }

//    @Override
    public List<Item> findAllOld(ItemSearchCond cond) {
        String itemName = cond.getItemName();
        Integer maxPrice = cond.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()
                .fetch();

        return result;
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {

        String itemName = cond.getItemName();
        Integer maxPrice = cond.getMaxPrice();

        return query.select(item)
                .from(item)
                .where(likeItemName(itemName), maxPrice(maxPrice))
                .fetch();
    }

    private BooleanExpression maxPrice(Integer maxPrice) {
        if (maxPrice != null) {
            return item.price.loe(maxPrice);
        }
        return null;
    }

    private BooleanExpression likeItemName(String itemName) {
        if (StringUtils.hasText(itemName)) {
            return item.itemName.like("%" + itemName + "%");
        }
        return null;
    }
}

 

공통

- Querydsl을 사용하려면 JPAQueryFactory가 필요하다. JPAQueryFactory는 JPA 쿼리인 JPQL을 만들기 때문에 EntityManager가 필요하다.

- 설정 방식은 JdbcTemplate을 설정하는 것과 유사하다.

- 참고로 JPAQueryFactory를 스프링 빈으로 등록해서 사용해도 된다.

 

save(), update(), findById()

기본 기능들은 JPA가 제공하는 기본 기능을 사용한다.

 

findAllOld

Querydsl을 사용해서 동적 쿼리 문제를 해결한다.

BooleanBuilder를 사용해서 원하는 where 조건들을 넣어주면 된다.

이 모든 것을 자바 코드로 작성하기 때문에 동적 쿼리를 매우 편리하게 작성할 수 있다.

 

findAll

앞서 findAllOld에서 작성한 코드를 깔끔하게 리팩토링 했다.

return query.select(item)
        .from(item)
        .where(likeItemName(itemName), maxPrice(maxPrice))
        .fetch();

- Querydsl에서 where(A, B)에 다양한 조건들을 직접 넣을 수 있는데, 이렇게 넣으면 AND 조건으로 처리된다. 참고로 where()에 null을 입력하면 해당 조건은 무시한다.

- 이 코드의 또 다른 장점은 likeItemName(), maxPrice()를 다른 쿼리를 작성할 때 재사용 할 수 있다는 점이다. 쉽게 이야기해서 쿼리 조건을 부분적으로 모듈화 할 수 있다. 자바 코드로 개발하기 때문에 얻을 수 있는 큰 장점이다.

@Configuration
@RequiredArgsConstructor
public class QuerydslConfig {

    private final EntityManager em;

    @Bean
    public ItemService itemService() {
        return new ItemServiceV1(itemRepository());
    }

    @Bean
    public ItemRepository itemRepository() {
        return new JpaItemRepositoryV3(em);
    }
}

 

@Import(QuerydslConfig.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {}

 

 

정리

Querydsl 덕분에 동적 쿼리를 매우 깔끔하게 사용할 수 있다.

return query.select(item)
        .from(item)
        .where(likeItemName(itemName), maxPrice(maxPrice))
        .fetch();

- 쿼리 문장에 오타가 있어도 컴파일 시점에 오류를 막을 수 있다.

- 메서드 추출을 통해서 코드를 재사용할 수 있다. 예를 들어서 여기서 만든 likeItemName(itemName), maxPrice(maxPrice) 메서드를 다른 쿼리에서도 함께 사용할 수 있다.

 

Querydsl을 사용해서 자바 코드로 쿼리를 작성하는 장점을 확인해봤다. 그리고 동적 쿼리 문제도 깔끔하게 해결하였다.

Querydsl은 이 외에도 수 많은 편리한 기능을 제공한다. 예를 들어 최적의 쿼리 결과를 만들기 위해서 DTO로 편리하게 조회하는 기능은 실무에서 자주 사용하는 기능이다. JPA를 사용한다면 스프링 데이터 JPA와 Querydsl은 실무의 다양한 문제를 편리하게 해결하기 위해 선택하는 기본 기술이라 생각하면 된다.

반응형