환경 설정
- build.gradle 파일에 QueryDSL 관련 설정을 해줍니다.
- 이후 compileQueryDsl을 실행시켜 주면 Q class가 생성됩니다.
- generated/querydsl에 Q class가 생성됩니다.
// queryDSL Configurtion, 맨 윗줄에 나와야 한다.
buildscript {
dependencies {
classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10")
}
}
...
apply plugin: "com.ewerk.gradle.plugins.querydsl"
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
dependencies {
...
//querydsl 추가
implementation 'com.querydsl:querydsl-jpa'
//querydsl 추가
implementation 'com.querydsl:querydsl-apt'
}
//querydsl 추가
//def querydslDir = 'src/main/generated'
// Q Class가 등록될 위치이다.
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
library = "com.querydsl:querydsl-apt"
jpa = true // Jpa 사용 유무를 명시한다.
querydslSourcesDir = querydslDir
}
sourceSets {
main {
java {
srcDirs = ['src/main/java', querydslDir]
}
}
}
compileQuerydsl{
options.annotationProcessorPath = configurations.querydsl
}
configurations {
querydsl.extendsFrom compileClasspath
}
간단한 QueryDSL 사용법
- 우선, JPAQueryFactory를 만들어 주어야 합니다.
- 이 때, EntityManager가 필요합니다.
- JPAQueryFactory의 경우는 따로 Configuration에서 Bean으로 등록하여 사용하겠습니다.
@Configuration
public class QueryDslConfig {
@PersistenceContext
EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
Autowired vs PersistenceContext
위 예제에서 보면 EntityManager를 주입받아 줄 때, Autowired가 아닌 다른 어노테이션으로 주입을 받는 것을 볼 수 있습니다. 다른 사람들의 코드를 보니 PersistenceContext로 주입을 받기는 하는데.. 개인적으로 생각해봤을 때는 Autowired와 다를게 없다고 생각해서 차이점을 좀 찾아보았습니다.
- Autowired는 스프링 빈을 주입받는데 사용하는 어노테이션입니다.
- PersistenceContext는 JPA 스펙에서 영속성 컨텍스트를 주입하는 표준 어노테이션입니다.
- 테스트해본 결과, 둘 다 정상적으로 동작합니다. 아무래도 QueryDsl은 JPA와 연관이 높아 보이니 이번 예제에서는 PersistenceContext로 주입받겠습니다.
데이터 가져오기 예시
- jpaQueryFactory를 의존성 주입받아 줍니다.
- 예시로는 이름으로 Professor를 조회해 옵니다.
- 이 때, 일반 클래스가 아닌 사전에 만들어 두었던 Q 클래스를 사용하여 조회하거나 조건을 명시합니다.
참고 사항
JPAQuery vs JPAQueryFactory
- 개인적인 생각으로는.. 취향차인거 같습니다. 성능은 차이가 없다고 합니다.
- 실제로 JPAQueryFactory 내부를 들어가도.. JPAQuery를 사용하고 있습니다. 직접 만드는것과 Factory를 거쳐서 만드는 것의 차이입니다.

실제로 두 방법을 모두 사용해서 작성해 보겠습니다.
@BeforeEach
void setUp() {
professorRepository.save(new Professor(null, "덕배"));
}
@Test
@DisplayName("한명의 professor를 조회한다.")
void queryDslNormalSelect() {
QProfessor professor = QProfessor.professor;
JPAQueryFactory queryFactory = new JPAQueryFactory(entityManager);
final Professor targetProfessor = queryFactory.selectFrom(professor)
.fetchOne();
System.out.println(targetProfessor.getName());
assertThat(targetProfessor.getName()).isEqualTo("덕배");
}
@Test
@DisplayName("vs JPAQuery")
void JPAQueryNormalSelect() {
JPAQuery<Professor> jpaQuery = new JPAQuery<>(entityManager);
QProfessor professor = QProfessor.professor;
final Professor professor1 = jpaQuery.select(professor)
.from(professor)
.fetchOne();
assertThat(professor1.getName()).isEqualTo("덕배");
}
두 방법 모두 정상적으로 동작하는걸 볼 수 있습니다. 단, QueryDSL을 사용하는 레포지토리가 따로 추가되는 문제점이 생겼습니다. 하나의 레포지토리로 QueryDSL로 작성한 기능 + JpaRepository의 기능을 모두 제공할 방법은 없을까요?
레포지토리가 여러개 생기는 문제점 해결
- 하나는 QueryDSL 전용 레포지토리, 하나는 Spring Data JPA 전용 레포지토리 총 두개의 레포지토리를 받아서 사용해야만 할까요?
- 이러한 문제를 해결하기 위해 Spring Data JPA에서는 커스텀 저장소를 구현할 수 있습니다.
내용을 요약하자면 다음과 같습니다.
- 사용자 정의 레포지토리 인터페이스를 생성합니다.
public interface QueryDslProfessorRepository {
Professor findByName(String name);
}
- 해당 인터페이스를 구현한 구현 클래스를 만듭니다. 단, 클래스 이름은 인터페이스명 + impl로 설정해 두어야 합니다. Postfix 변경을 원한다면 @EnableJpaRepositories 어노테이션에서 설정할 수 있습니다.
@Repository
public class QueryDslProfessorRepositoryImpl implements QueryDslProfessorRepository {
private final JPAQueryFactory jpaQueryFactory;
public QueryDslProfessorRepositoryImpl(final JPAQueryFactory jpaQueryFactory) {
this.jpaQueryFactory = jpaQueryFactory;
}
@Override
public Professor findByName(final String name) {
final Professor professor = jpaQueryFactory.selectFrom(QProfessor.professor)
.where(QProfessor.professor.name.eq(name))
.fetchOne();
return professor;
}
...
}
- 기존 레포지토리에서 사용자 정의 레포지토리 인터페이스를 상속받도록 처리합니다.

- 하나의 인터페이스로 QueryDSL 기능과 JpaRepository의 기능을 모두 사용할 수 있습니다.
@DisplayName("QueryDsl로 만들어진 쿼리를 테스트한다.")
@Test
void queryDslQueryTest() {
final Professor professorA = professorRepository.save(new Professor(null, "professorA"));
final Lecture lecture = lectureService.makeLecture(new Lecture(null, professorA, "lectureA"));
entityManager.flush();
entityManager.clear();
final Professor professor = professorRepository.findByName("professorA");
assertThat(professor.getName()).isEqualTo("professorA");
}
출처
- 자바 ORM 표준 JPA 프로그래밍
- https://jojoldu.tistory.com/372
'프레임워크 > Spring' 카테고리의 다른 글
Redis 간단하게 사용해 보기 (1) | 2023.04.09 |
---|---|
Jpa의 @Query 탐험기 (0) | 2022.11.19 |
프로젝트에서의 동시성 문제 해결기(낙관적 락, 비관적 락) (0) | 2022.11.16 |
N+1 문제와 해결 방법 (0) | 2022.11.06 |
스프링 이벤트를 통한 양방향 의존성 풀기 (0) | 2022.11.04 |