환경 설정

  • 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를 거쳐서 만드는 것의 차이입니다.

JPAQueryFactory의 메서드

 

실제로 두 방법을 모두 사용해서 작성해 보겠습니다.

@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의 기능을 모두 제공할 방법은 없을까요?

 

레포지토리가 여러개 생기는 문제점 해결

  1. 하나는 QueryDSL 전용 레포지토리, 하나는 Spring Data JPA 전용 레포지토리 총 두개의 레포지토리를 받아서 사용해야만 할까요?
  2. 이러한 문제를 해결하기 위해 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");
}

 

출처

+ Recent posts