QueryDsl서브쿼리를 사용하는 방법에 대해서 내용을 한번 정리해보려고 한다.
회사 내부 신규 프로젝트의 경우 Spring Boot + JPA를 기반으로 프로젝트를 진행하고 있다.
내부적으로 쿼리 작업을 하면서 Spring Data JPA에서 기본적으로 제공해 주는 CRUD 메서드를 사용하더라도 데이터를 얻는 데에 많은 어려움이 존재했고, QueryDsl 라이브러리를 통해서 원하는 데이터를 얻어내고 있다.
그중 많이 사용하지만 매번 까먹는 내용인 서브 쿼리 사용 방법에 대해서 정리를 하려고 한다.😀
⚡QClass 생성
가장 기본적으로 QueryDsl을 사용하려면 먼저 QClass를 생성해야 한다. QClass는 Querydsl에서 사용하는 엔티티의 필드와 연산자에 대한 메타 정보를 제공하는 클래스이다. 이를 이용하여 타입 안정성을 보장하면서 쿼리를 작성할 수 있다.
(초반에 QClass가 정확히 뭔지 몰라서 많이 찾아봤던 기억 때문에 정리 하려고 한다. 😅)
❗ QClass를 생성하는 방법
프로젝트 내부에 Querydsl 관련 라이브러리를 추가해야한다.
프로젝트를 초기 구축할때 기본적으로 넣어야 되는 부분 기준으로만 정리하려고 한다.
/* build.gradle 기준 */
dependencies {
/* Spring boot starter */
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
/* QueryDsl */
implementation("com.querydsl:querydsl-jpa:5.0.0")
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jpa"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
}
맨 처음 설정을 하고 사용할때 QClass가 만들어지지 않고 에러가 났으며, 심지어 생성이 되더라도 왜 생성이 되지?라는 의문이 생기게 되었고 공식 문서부터 이곳저곳에 정리해놓은 글을 찾아보고 해결했던 경험이 있었다.
❗QClass란?
엔티티 클래스의 메타 정보를 담고 있는 클래스로, Querydsl은 이를 이용하여 타입 안정성을 보장하면서 쿼리를 작성할 수 있게 된다. 즉, QClass를 이용하면 문자열로 쿼리를 작성하는 것이 아니라, Java 코드로 쿼리를 작성할 수 있게 된다.
QClass는 Querydsl Annotation Processor를 이용하여 생성된다. Annotation Processor는 컴파일 타임에 Java 코드를 처리하는 데 사용되며, Querydsl에서는 엔티티 클래스에 지정된 Annotation 정보를 바탕으로 QClass를 생성한다.
Annotation Processor는 컴파일러에 의해 실행되며, 컴파일 타임에 클래스와 메타데이터를 분석하여 자동으로 코드를 생성한다. Querydsl Annotation Processor는 엔티티 클래스에 지정된 @Entity와 @QueryEntity를 분석하여 QClass를 생성합니다.
즉!!! 컴파일러는 소스코드를 컴파일할 때마다 Annotation Processor를 실행하여 추가적인 작업을 수행한다. 이때 QClass를 생성한다는 것을 알 수 있다.
❗참조 링크
⚡서브 쿼리 사용 방법 정리
바로 서브 쿼리를 사용 하는 방법에 대해서 정리를 하려고 한다.
기본적으로 JPAQueryFactory, Projections, ExpressionUtils 클래스는 자주 사용하기 때문에 뭔지 공부할 필요성이 있다고 생각한다. (따로 정리 중📒)
❗select 서브 쿼리
QUser qUser = QUser.user;
QGroup qGroup = QGroup.group;
@Service
@RequiredArgsConstructor
public class TestQueryDsl() {
private final JPAQueryFactory queryFactory;
public void TestSelectSubQuery() {
List<Tuple> result = queryFactory
.select(
qUser.userName,
qUser.views,
JPAExpressions.select(qGroup.groupName)
.from(qGroup)
)
.from(board)
.fetch();
}
}
만약 같은 Entity를 사용한다면 별칭이 겹치면 안되기 때문에 QClass 객체를 하나 더 생성해서 사용해야 한다.
위의 내용은 완전 기본적으로 서브 쿼리를 사용하는 방식이고 내가 작업할때 사용하는 방식으로 한번 작성하도록 하겠다.
@Transactional(readOnly = true)
public List<Clent> getClientList() {
BooleanBuilder whereOption = new BooleanBuilder();
// 검색 조건이 필요하다면 추가
// whereOption(Quser.user.userId.eq("hyuntae"));
return queryFactory
.select(
Projections.fields(
Client.class,
Quserinfo.userinfo.uid.as("userId"),
Quserinfo.userinfo.userName.as("userName"),
ExpressionUtils.as(
JPAExpressions.select(Qorgnz_grp.orgnz_grp.ogNm)
.from(Qorgnz_grp.orgnz_grp)
, "groupName"
)
)
)
.from(Quserinfo.userinfo)
.where(whereOption)
.fetch();
}
예시 샘플을 한번 작성해 보았다.
Projections 를 통해서 결과를 특정 클래스나 인터페이스로 바인딩을 해서 사용을 합니다. 해당 기능을 사용하면 다음과 같은 이점이 존재합니다.
- 쿼리 결과를 DTO나 VO 등의 클래스로 변환하여 반환할 수 있다.
- 쿼리 결과를 엔티티 객체가 아닌 일반적인 Java 객체로 조회할 수 있다.
- 쿼리 결과에서 필요한 속성만 선택적으로 조회할 수 있다.
테스트 소스에서는 ExpressionUtils을 통해서는 alias를 시켜서 사용했지만 조건절 생성(BooleanBuilder), 문자열 조합, 집계 함수, 날짜 연산등의 다양한 메서드를 사용해서 개발을 진행하고 있습니다.
❗where 서브 쿼리
select 서브 쿼리와 크게 다른 점은 존재하지 않는다. alias 사용하지 않는다? 이 정도밖에 없는 것으로 판단이 된다. 개발을 진행하면서 보통 where 조건에 서브 쿼리를 사용하는 경우는 별로 없었던 거 같다. 기억이 나지 않는 걸 보니
위의 예시 샘플을 기반으로 where 서브 쿼리를 한번 작성해 보도록 하겠다.
// 조건절 생성
BooleanBuilder whereOption = new BooleanBuilder();
whereOption(Quser.user.userId.eq(
JPAExpressions
.select(QsystemInfo.systemInfo.userId)
.from(QsystemInfo.systemInfo)
.where(QsystemInfo.systemInfo.systemKey.eq(systemKey))
));
ExpressionUtils의 조건절 생성을 통해서 한번 where 서브 쿼리를 한번 작성해 보았다. (select 서브 쿼리와 별로 다른 점이 없..😅)
개발을 진행하면서 서브 쿼리가 필요한 경우, 까먹은 경우에 한 번 더 내용을 찾아보려고 정리를 해 보았다.
⚡마무리 정리
select 조회 시 서브 쿼리를 사용하는 경우는 생각보다 많은 경우가 있었다. 하지만 where 절에서 서브 쿼리를 사용하는 경우는 개발을 진행하면서 생각이 나지 않을 정도로 사용을 해본 적이 없었다. 회사의 레거시 시스템을 보면 종종 where 절에서 서브 쿼리를 사용하는 경우를 찾아볼 수 있었지만 결코 좋은 방식은 아니라는 걸 알 수 있다. 실제 성능을 비교해봐도 서브 쿼리를 사용하는 경우는 느리다고 하니 참조해서 사용하면 좋을 거 같다.
'Spring' 카테고리의 다른 글
[Spring Boot] application.yml 설정 파일 우선순위에 대한 내용 정리 (1) | 2023.03.07 |
---|---|
[Spring Boot] 콘솔에 출력된 배너 변경하기 (0) | 2023.02.12 |
[Spring] SpringBoot + RabbitMQ 연동 내용 정리. (0) | 2022.10.21 |
Spring Boot SPQR 개념 및 사용 방법 정리 (0) | 2022.09.15 |
Spring Boot JPA 사용 방법 정리 (0) | 2022.08.04 |