Projects/[Spring] Code Refactoring Project

[Spring Plus] JPQL을 Querydsl로 변환하기

montmer27 2026. 3. 4. 20:33

목표

JPQL로 작성된 아래 메서드의 쿼리문을 Querydsl로 변환하자.
 
 

 


상황

1. Todo 조회 시 연관된 엔티티인 user를 left join으로 가져옴: N+1 문제 발생 가능

TodoRepository.java

2. Querydsl 관련 종속성 및 QuerydslConfig 없음: 추가 필요

3. 연관 객체로 가져오는 user의 데이터 중에서 일부만 사용하여 dto로 재가공: db에서 조회할 때부터 dto projection 적용

 


해결 방법

1. build.gradle에 종속성 추가

build.gradle

 
2. QuerydslConfig.java 파일 추가

QuerydslConfig.java

 
3. TodoCustomRepository.java 및 Querydsl 적용할 db 조회 메서드 생성

TodoCustomRepository.java

 
4. 3의 구현체인 TodoCustomRepositoryImpl 및 Querydsl 쿼리문 작성

TodoCustomRepositoryImpl.java

 
5. 기존 TodoRepository에 interface 상속 추가

결과

정상 조회 및. 쿼리도 1번만 실행

Postman 호출 화면

 

쿼리 1회 콘솔 화면


개선

현재 Querydsl문은 Projections.constructor()를 이용하여 todo 엔티티로부터 6개의 필드를 타입 순서를 맞춰 생성자 파라미터에 넣어줘야만 한다. 필드가 많으므로 필드 주입 순서가 달라질 경우 런타임에 문제가 발생할 수 있다.
따라서 컴파일 타임에 파라미터 타입과 순서 오류를 잡아주는 @QueryProjection으로 변경함으로써 컴파일 타입 안전성을 높여보자. 물론 이 방법에도 장단점이 있으므로 알고 가는 것이 좋다.
 
@QueryProjection 장단점 비교
장점: 잘못된 타입/순서를 컴파일 타임에 확인 가능
단점: DTO가 Querydsl에 의존하게 됨
 

1. UserResponse, TodoResponse DTO 클래스 생성자에 @QueryProjection 어노테이션 적용

UserResponse.java

 

TodoResponse.java

 

2. 문제 발생: QUserResponse 부분에서 컴파일 에러 발생

3. 원인 분석

 
Q클래스는 빌드 시점에 APT가 자동 생성하는데, QUserResponse를 TodoCustomRepository.findByTodoId() 메서드가 이미 참조하고 있어 컴파일 자체가 실패 -> Q클래스 생성 불가 -> 악순환

3. 해결:

해당 파일 전체를 주석 처리, ./gradlew compileJava 실행: 참조 코드가 없는 상태에서 빌드 성공. APT가 Q클래스 생성. 이후. 주석 복구
 

문제 원인 상세

1단계: TodoResponse만 Q클래스로 바꾸고 빌드 -> 성공

 
2단계: UserResponse도 Q클래스로 바꾸려고 보니 컴파일러가 인식을 못함 -> 그런데 컴파일을 해야 생성됨 -> 악순환

[타임라인]

[1단계] Projections.constructor() → QTodoResponse로 교체 후 빌드
→ QTodoResponse 생성 성공 ✅

[2단계] 내부의 UserResponse도 QUserResponse로 교체
→ 이 시점에 QUserResponse는 아직 생성된 적 없음
→ QUserResponse를 참조하는 코드 때문에 컴파일 실패
→ QUserResponse 생성 불가 (악순환) ❌

인사이트

Q클래스는 컴파일 시점에 APT(Annotation Processing Tool)에 의하여 생성됨.
생성되기도 전에 컴파일 에러가 나는 경우가 있음.
그럴 땐 해당 코드 전체를 주석 처리 후 ./gradlew compileJava를 수행하면 일단 생성은 됨.