본문 바로가기
별밤 일지/개발

[QueryDSL] queryDSL 도입기

by 별밤 에디터 2024. 1. 28.

 

저번에 사용했던 JPA Specification에 이어서 이번에는 QueryDSL을 도입해 보려고 합니다.

QueryDSL

자바 코드로 SQL과 같은 쿼리를 생성해 주는 프레임워크로 기본적으로 @Entity로 등록된 클래스들을 Q클래스로 생성해 사용합니다.

 

JPA를 사용하다 쿼리가 복잡해질 때는 JPQL을 사용하거나 저번에 소개한 Specification을 사용할 수 있습니다.

하지만 다음과 같은 장점 때문에 QueryDSL을 많이 사용합니다.

  • java 코드로 가독성 좋은 쿼리 조건 작성
  • 컴파일 시점에 문법 오류 발견 가능
  • 자동완성 편리
  • 동적인 쿼리 작성 가능 (BoleanBuilder, BooleanExpression 활용)

결국 자바로 작성하기 때문에 작성 중에 IDE에서 컴파일 오류를 잡아줄 뿐만 아니라 자동완성이 가능하다는 점이 가장 큰 장점으로 느껴졌습니다.

 


 

▶ 설정 방법

여태 적용했던 라이브러리들과는 좀 다른 추가적인 설정이 필요합니다. 또한 IDE, SpringBoot 버전에 따라 다르게 설정되기도 하기 때문에 자신의 환경에 맞게 설정하셔야 합니다.

 

plugins {
...
	id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" //Gradle플러그인 설정
}

def querydslDir = "$buildDir/generated/querydsl" //Q클래스가 생성될 위치

sourceSets{
	main{
		java {
			srcDirs 'src/main/java', querydslDir //생성된 Q클래스 위치를 source폴더로 지정
		}
	...
	}
}
dependencies {
  implementation "com.querydsl:querydsl-jpa:5.0.0"
	annotationProcessor "com.querydsl:querydsl-apt:5.0.0"
}

querydsl { //QueryDSL설정
	jpa = true //JPA사용여부
	querydslSourcesDir = querydslDir //Q클래스 생성위치 지정
}
configurations {
	querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
	options.annotationProcessorPath = configurations.querydsl
}

 

 

  • QuerydslConfiguration.java
@Configuration
public class QuerydslConfiguration {

    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }

}

 

JPAQueryFactory를 Bean으로 등록해야 프로젝트 전역에서 주입받아 QueryDSL 사용이 가능합니다.

 

 


 

사용예시

다음은 SpringDataJPA와 함께 사용하는 방법으로 작성되었습니다.

 

  • Repository.java
public interface ObservationalFitRepository extends JpaRepository<ObservationalFit, Long>, ObservationalFitRepositoryCustom{
  ...
}

 

SpringDataJPA를 사용하기 위해 JpaRepository를 extends한 상태에서
QueryDSL을 사용해 작성한 RepositoryCustom 인터페이스도 같이 extends합니다.

 

 

 

  • RepositoryCustom.java
public interface ObservationalFitRepositoryCustom {

    List<Long> getObservationIdsByBestFit(String date);
}

 

QueryDSL을 구현하기 위한 Interaface를 먼저 작성합니다.

 

 

 

  • RepositoryCustomImpl.java
@RequiredArgsConstructor
public class ObservationalFitRepositoryCustomImpl implements ObservationalFitRepositoryCustom{

    private final JPAQueryFactory query;
    QObservationalFit observationalFit = QObservationalFit.observationalFit;

    @Override
    public List<Long> getObservationIdsByBestFit(String date) {
        return query
                .select(observationalFit.observationCode)
                .from(observationalFit)
                .where(observationalFit.date.eq(date))
                .orderBy(observationalFit.bestObservationalFit.desc())
                .limit(10)
                .fetch();
    }
}

 

CustomRepository의 메소드를 구현합니다.

JPAQueryFactory를 주입 받기 위해 생성자가 필요한데 여기서는 @RequiredArgsConstructor로 대체했습니다.

각 테이블의 컬럼은 생성된 Q클래스로 접근할 수 있고 쿼리의 조건은 자바 개발자라면 무리 없이 어떤 쿼리인지 이해할 수 있을 것 같습니다.

-fetch() : 반환되는 값을 리스트로 가져온다. 이때, 반환되는 값이 없으면 빈 리스트를 반환한다.

 

 

 

  • 데이터 조회 시 DTO 사용방법

select를 할 때 DTO를 사용하면 모든 column을 조회하지 않아도 되기 때문에 성능을 높일 수 있습니다.

DTO클래스로 반환하기 위해서는 마찬가지 Q클래스를 사용해야 하므로 DTO클래스도 Q클래스로 생성될 수 있게 합니다.

 

 

 

  • DTO 클래스
public class ObservationSimpleParams {
    private Long itemId; //관측지id
    private String thumbnail; //썸네일
  ...

    @QueryProjection
    public ObservationSimpleParams(Long itemId, String thumbnail, String title, String address, String intro, Double longitude, Double latitude, Double light, Double observeFit, Long saved) {
        this.itemId = itemId;
        this.thumbnail = thumbnail;
        ...
    }
}

 

@QueryProjection 어노테이션을 생성자에 추가하면 해당 생성자로 Q클래스가 생성됩니다.
 
 
 
 
  • CustomRepositoryImpl
@Override
    public List<ObservationSimpleParams> findObservationSimpleByIdList(List<Long> observationIds, String date) {
        return query
                .select(
                new QObservationSimpleParams(observation.observationId, observeImage.image.max(),
                observation.observationName, observation.address,observation.intro, observation.longitude,
                observation.latitude, observation.light, observationalFit.bestObservationalFit, observation.saved))
                .from(observation)
                ....
    }

 

select 절에 생성한 Q클래스의 생성자를 호출하면 해당 DTO로 값을 가져올 수 있습니다.


JPA 관련 프레임워크들을 사용해 보면서,,,

이렇게 JPA에서 Specification을 사용해서 기존 쿼리를 튜닝하고, QueryDSL도 적용해 보았습니다.

기존에 JPQL을 사용해서 작성한 쿼리도 있어 JPQL, JPA Specification, Query DSL을 간단하게 비교해 보자면 다음과 같습니다.

  • JPQL
    • 간단한 조건이 들어간 쿼리를 쓰고 싶으나 JPA메소드가 길어지는 것이 싫은 경우 사용
    • 네이티브 쿼리가 익숙한 개발자가 추가 학습할 시간이 없을 때 추천
  • QueryDSL
    • 동적 쿼리가 필요한 경우
    • 복잡한 조건의 쿼리를 자주 사용해야 하는 경우
    • 쿼리 작성에 익숙하지 않아 IDE의 도움을 받고 싶을 경우
  • JPA Specification
    • 동적으로 service단에서 조건을 추가하고 싶은 경우
    • 어렵고 보기 힘든 코드를 작성하고 싶을 때…

저번 글에도 말씀드렸던 것처럼 저는 굳이 Specification 사용을 권장하지 않습니다. JPA와 QueryDSL만으로도 충분히 보기 좋은 쿼리를 작성할 수 있기 때문입니다.

저번에 작성했던 Specification을 사용한 코드는 조만간 QueryDSL로 변경해서 가시성을 비교해 보겠습니다.

 


별헤는밤 사용해보기

 

별 헤는 밤: 밤하늘, 별자리, 여행정보와 날씨예보까지 - Google Play 앱

오늘부터 별잘알! 오늘밤 별자리 정보, 날씨·광공해·월령까지 고려한 '관측적합도', 주변 관측지 검색으로 누구나 쉽게 밤하늘을 즐겨보세요.

play.google.com