: 다양한 데이터베이스 플랫폼에 접근하여 SQL과 유사한 문법으로 쿼리를 작성하여 데이터 처리를 수행하는 데 도움을 주는 프레임워크이다.
쿼리 전용 클래스(Q클래스)로 재구성해주는 기술: 쿼리 타입 혹은 Q-Class 라는 이름으로 불리며, '도메인 모델의 구조'를 나타낸다.
주로 도메인 클래스에 대한 쿼리를 작성할 때 사용되며 도메인 클래스의 각 필드를 변수로 가짐
-> QueryDSL에서 데이터베이스 테이블을 객체처럼 다룰 수 있도록 만들어진 QueryDSL 전용 클래스!
일반 Entity가 QueryDSL을 사용하기 위한 메타 모델로 만들어지는 클래스라고 생각하면 됨.
-> 내가 직접 구현하지 않아도 된다.
컴파일 실행
build.gradle 파일 내에 작성한 태스크가 수행됨
컴파일을 수행하는 과정에서 코드 생성 기구(Annotation Processing Tool)가 수행됨
build.gradle에 작성한 QueryDSL의 annotation processor가 수행
컴파일이 완료되면 Q-Class가 자동 생성 끝!
아래에서 QueryDSL을 직접 코드에 적용해보며 보여주겠다.
컴파일 시점에 오류를 발견할 수 있고, 자바 코드를 사용하기 때문에 인텔리제이의 자동완성 기능까지 활용할 수 있다는 것이 큰 장점이다!dependencies {
//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"
}


화면 오른쪽 Gradle 탭(코끼리 모양)의 Tasks에서
-> [build/clean]
-> [other/compileJava]
를 순서대로 실행시켜준다. (더블클릭!)

빌드가 끝난 후, build 디렉토리에 entity별 Q-Class가 자동생성된 것을 확인할 수 있다!👏🏻
@Configuration
public class JPAConfiguration {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
JPAQueryFactory 는 QueryDSL에서 제공하는 주요 클래스 중 하나이다.
public interface TodoRepository extends JpaRepository<Todo, Long>, TodoQueryRepository {
@Query("SELECT t FROM Todo t " +
"LEFT JOIN t.user " +
"WHERE t.id = :todoId")
Optional<Todo> findByIdWithUser(@Param("todoId") Long todoId);
}
기존 JpaRepository 를 사용한 findByIdWithUser 메서드이다.
JPQL로 간단하게 구현이 가능하지만, QueryDSL로 바꾸어 보겠다.
JpaRepository 인터페이스에서는 QueryDSL을 직접 사용하지 않고,
별도의 새로운 인터페이스를 만들어서 QueryDSL을 적용할 메서드를 선언해주었다.
public interface TodoQueryRepository {
Optional<Todo> findByIdWithUser(Long todoId);
}
아래는 실제 QueryDSL을 사용할 Repository 구현체이다.
@Repository
public class TodoQueryRepositoryImpl implements TodoQueryRepository{
private final JPAQueryFactory jpaQueryFactory;
public TodoQueryRepositoryImpl(JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;
}
@Override
public Optional<Todo> findByIdWithUser(Long todoId){
Todo result = jpaQueryFactory
.selectFrom(todo)
.leftJoin(todo.user, user).fetchJoin()
.where(todo.id.eq(todoId))
.fetchOne();
return Optional.ofNullable(result);
}
}
selectfrom(todo)
→ select(todo) / from(todo) 으로 나누어 작성해도 됨!
leftJoin(todo.user, user).fetchJoin()
→ Todo와 User를 LEFT JOIN하여 연관된 데이터를 한 번에 가져오기
→ N+1 문제를 방지하기 위한 fetchjoin()
where(todo.id.eq(todoId))
→ 특정 todoId에 해당하는 Todo를 필터링하기 (sql의 where절)
fetchOne()
→ 단일 객체 조회
→ 다건 조회 시 fetch() 사용
Optional.ofNullable(result)
→ 결과가 null일 수도 있으므로 Optional로 감싸서 반환
기존 TodoRepository에 QueryDSL을 사용한 커스텀 repository를 상속시켜준다.
public interface TodoRepository extends JpaRepository<Todo, Long>, TodoQueryRepository {
}
public TodoResponse getTodo(long todoId) {
Todo todo = todoRepository.findByIdWithUser(todoId)
.orElseThrow(() -> new InvalidRequestException("Todo not found"));
User user = todo.getUser();
return new TodoResponse(
todo.getId(),
todo.getTitle(),
todo.getContents(),
todo.getWeather(),
new UserResponse(user.getId(), user.getEmail()),
todo.getCreatedAt(),
todo.getModifiedAt()
);
}
이번 메서드는 간단한 쿼리였기 때문에 금방 구현이 끝났다.
하지만 더욱 복잡한 쿼리를 구현하게 된다면 QueryDSL이 좋은 이유를 체감하게 될 것이다.
| 방식 | 특징 | 장점 | 단점 |
|---|---|---|---|
| JPQL (@Query) | 문자열 기반 쿼리 | 간단한 조회 쿼리 작성 가능 | - 컴파일 타임 검증 없음 - 동적 쿼리 작성 어려움 |
| QueryDSL | Java 코드 기반 쿼리 DSL | - 컴파일 타임 검증 가능 - 가독성 및 유지보수성 증가 - 동적 쿼리 작성 용이 | 초기 설정 필요 (Q-Class 생성 등) |