본문 바로가기

SearchDeveloper/SpringBoot

[JPA] Querydsl 시작하기

Querydsl 이란?

JPQL 생성을 도와주는 라이브러리이다. Querydsl 메소드를 사용해 작성한 쿼리는 JPQL 로 변환되어 SQL 쿼리가 실행된다.

사용하는 이유

  1. 쿼리 메소드는 메소드명만으로 하이버네이트가 SQL로 변환해주기 때문에 간단하게 사용할 때는 너무나 강력하다. 하지만 join 을 쓰거나 쿼리가 복잡해지면 JPQL 로도 결국 SQL 쿼리 짜듯이 문자열로 작성하는 수 밖에 없다. 하지만 Querydsl 를 사용하면 여전히 메소드를 활용해 쿼리를 생성하는 것이 가능하다.
  2. @Query 애노테이션으로 JPQL을 짜면 syntax error를 애플리케이션 실행을 해봐야 알 수 있다. 하지만 Querydsl를 사용하면 메소드와 변수로 쿼리를 만들기 때문에 애초에 syntax error 나기가 힘들다!
  3. 조건 분기에 따라 메소드 형태로 동적 쿼리 생성이 가능해 가독성이 좋다.

버전 정보

그래들 7.5

스프링부트 2.7.4

java 11

querydsl 5.0.0

빌드를 해보자!

Querydsl 은 엔티티 클래스와 직접 접근하는게 아니라 접두어 ‘Q’가 붙은 Q클래스로 엔티티의 필드를 대신 접근한다. Q클래스는 우리가 직접 생성하는 게 아니라 빌드하면 자동으로 생성되는 파일이다. 그럼 Q클래스 생성 + querydsl 을 사용할 수 있도록 빌드 설정을 해보자

build.gradle

plugins {
    id 'org.springframework.boot' version '2.7.4'
    id 'io.spring.dependency-management' version '1.0.14.RELEASE'
    id 'java'
}

group = 'dev.hyein'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {                                                      // [1]
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    // spring boot
    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

    // jpa
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    runtimeOnly 'com.h2database:h2'

    // lombok
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

    // querydsl                                                       // [2]   
    implementation group: 'com.querydsl', name: 'querydsl-jpa', version: '5.0.0'     // (1)
    annotationProcessor("com.querydsl:querydsl-apt:5.0.0:jpa")                       // (2) 
    annotationProcessor "jakarta.persistence:jakarta.persistence-api"                // (3) 

    // test
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}
  • [1]: compile 시 annntationProcessor 를 사용할 수 있도록 상속받는다. lombok 이 포함된 프로젝트이면 자동으로 추가되었을 것이다.
  • [2]: Querydsl 를 사용할 수 있도록 디펜던시를 추가한다.
    • (1) Querydsl + JPA 라이브러리
    • (2) Q클래스 생성할 때 필요하다. @Entity 가 붙은 클래스를 찾아 Q클래스 파일을 생성해준다.
    • (3) 빌드 시 NoClassDefFoundError 익셉션이 나는 경우 추가해준다.

Q클래스 생성 위치 설정

Intelij settings

Intelij settings 의 Build, Execution, Deployment > Build Tools > Gradle 에서 빌드와 실행을 Gradle 로 할 건지, Intelij 로 할 건지 선택할 수 있다.

Build and run usingGradle 로 설정하면 Q 클래스 파일은build/generated 에 생성되고,
Intelij
로 설정하면 source/main/generated 에 생성된다.

Q클래스는 개발자가 수정하거나 생성하는 파일이 아니기 때문에 나는 build/generated 에 생성되도록 Gradle 로 그대로 두었다.

그리고 build task를 실행하면, 아래 위치에 Entity 클래스와 매핑되는 Q클래스가 생성된 것을 볼 수 있다.

Q Class

 

annotationProcessor 가 생성해준 QMember.java

QMember.java

Querydsl 사용해보기

Query Factory 생성

@Configuration
public class QuerydslConfig {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory() {
        return new JPAQueryFactory(entityManager);
    }
}

@PersistenceContext : EntityManager를 빈으로 주입해준다. 뭔가 이상하지 않은가? 빈이란 것은 하나의 싱글톤 객체로 생성되어 재사용되는데, EntityManager 는 커넥션 풀 관리 등의 역할을 하기 때문에 원체 재사용하거나 스레드 끼리 공유하면 안 되는데 말이다. 그래서 @PersistenceContext 를 선언하면 중간에 프록시가 새 EntityManager 를 생성해서 반환해주어 thread-safe 를 보장한다.

JPAQueryFactory : Querydsl 생성 후 EntityManager 를 통해 쿼리 결과를 반환할 수 있도록 JPAQueryFactory 빈을 선언한다.

커스텀 Repository 생성

Querydsl 도 쓰고 싶지만, spring-data-jpa 가 생성해주는 쿼리 메소드도 포기하고 싶지 않다. 그럴 땐, 커스텀 Repository를 만들어 두 마리 토끼를 다 잡을 수 있다!

 

먼저 간단한 Member Entity

@Entity
@Table(name = "MEMBER")
@NoArgsConstructor @AllArgsConstructor
@Getter
public class Member {
    @Id
    private String id;
    private String name;
    private Integer age;
}

 

MemberRepository.java

public interface MemberRepository extends JpaRepository<Member, String>, MemberRepositoryCustom {
        Optional<Member> findById(String id);
}

익숙한 Repository interface 다. JpaRepository interface 와 더불어 Querydsl을 사용할 공간인 MemberRepositoryCustom interface 를 추가로 상속받고 있다.

 

MemberRepositoryCustom.java

public interface MemberRepositoryCustom {
    Member searchById(String id);
}

interface 에 querydsl로 만들고 싶은 메소드를 선언해준다.

이제 MemberRepositoryCustomImpl 에서 querydsl 를 사용하는 구현체를 만들텐데, 그 전에 다이어그램을 쑥 훑어보고 가자!

Repository diagram

MemberRepositoryCustomImpl.java

@Repository                                                    // [1]
@RequiredArgsConstructor
public class MemberRepositoryCustomImpl implements MemberRepositoryCustom{
    private final JPAQueryFactory jpaQueryFactory;             // [2]

    private static final QMember member = QMember.member;      // [3]

    @Override
    public Member searchById(String id) {
        return jpaQueryFactory                                 // [4]
                .selectFrom(member)
                .where(member.id.eq("1"))
                .fetchOne()
                ;
    }
}

위에서 생성한 MemberRepositoryCustom interface 의 구현체이다. MemberRepository 에서 JpaRepository interface를 상속받기만 하면 spring-data-jpa가 구현체를 생성해주듯이, MemberRepository 에서 MemberRepositoryCustom interface 를 상속받으면 MemberRepositoryCustomImpl 를 구현체로 인식해 MemberRepository 에서 JPA 가 제공해주는 쿼리 메소드와 내가 만든 쿼리 메소드를 동시에 사용할 수 있다.

💡 어떤 글에서는 구현체인 MemberRepositoryCustomImpl 의 클래스명을 커스텀 interface 명 (MemberRepositoryCustom) + ‘Impl’ 로 해야 구현체로 인식이 된다 하지만, MemberRepositoryCustomImpl 를 다른 이름으로 변경해도 정상적으로 테스트가 성공하였다.
  • [1] : JPAQueryFactory 빈을 사용하기 위해 @Repository 를 선언한다.
  • [2] : JPAQueryFactory 빈을 선언한다.
  • [3] : Querydsl 의 필드 접근에 사용할 Q클래스를 선언한다.
  • [4] : jpaQueryFactory 로 원하는 Querydsl 을 생성한다. id 가 “1” 인 member 엔티티 하나를 가져오는 쿼리이다.

실제로 생성되는 SQL은 다음과 같다.

select member0_.id as id1_0_, member0_.age as age2_0_, member0_.name as name3_0_ 
from member member0_ 
where member0_.id=?

binding parameter [1] as [VARCHAR] - [1]

쿼리 메소드 호출하기

querydsl 로 만든 쿼리 메소드가 잘 동작하는지 테스트해보자

@SpringBootTest
class MemberRepositoryCustomImplTest {
    @Autowired
    MemberRepository memberRepository;

    @Test
    @DisplayName("id 로 Member 찾기")
    void findMemberById() {
        String id = "1";

        Member member = memberRepository.searchById(id);
        Assertions.assertNotNull(member);
        Assertions.assertEquals(id, member.getId());
    }
}

memberRepository 빈에서 쿼리 메소드를 호출하면 된다.

깃 소스

https://github.com/AwesomeHye/spring-data-jpa-demo

 

GitHub - AwesomeHye/spring-data-jpa-demo: spring-data-jpa 데모

spring-data-jpa 데모. Contribute to AwesomeHye/spring-data-jpa-demo development by creating an account on GitHub.

github.com

 

 

레퍼런스

https://madplay.github.io/post/introduction-to-querydsl 

https://velog.io/@jkijki12/Spring-QueryDSL-%EC%99%84%EB%B2%BD-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0#q-%ED%81%B4%EB%9E%98%EC%8A%A4-%EC%83%9D%EC%84%B1

https://jojoldu.tistory.com/372

http://honeymon.io/tech/2020/07/09/gradle-annotation-processor-with-querydsl.html

[JPA] Querydsl 시작하기

 

글 읽어주셔서 언제나 감사합니다. 좋은 피드백, 개선 피드백 너무나도 환영합니다.