Querydsl 이란?
JPQL 생성을 도와주는 라이브러리이다. Querydsl 메소드를 사용해 작성한 쿼리는 JPQL 로 변환되어 SQL 쿼리가 실행된다.
사용하는 이유
- 쿼리 메소드는 메소드명만으로 하이버네이트가 SQL로 변환해주기 때문에 간단하게 사용할 때는 너무나 강력하다. 하지만 join 을 쓰거나 쿼리가 복잡해지면 JPQL 로도 결국 SQL 쿼리 짜듯이 문자열로 작성하는 수 밖에 없다. 하지만 Querydsl 를 사용하면 여전히 메소드를 활용해 쿼리를 생성하는 것이 가능하다.
- @Query 애노테이션으로 JPQL을 짜면 syntax error를 애플리케이션 실행을 해봐야 알 수 있다. 하지만 Querydsl를 사용하면 메소드와 변수로 쿼리를 만들기 때문에 애초에 syntax error 나기가 힘들다!
- 조건 분기에 따라 메소드 형태로 동적 쿼리 생성이 가능해 가독성이 좋다.
버전 정보
그래들 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 의 Build, Execution, Deployment > Build Tools > Gradle
에서 빌드와 실행을 Gradle 로 할 건지, Intelij 로 할 건지 선택할 수 있다.
Build and run using 을 Gradle 로 설정하면 Q 클래스 파일은build/generated
에 생성되고,
Intelij 로 설정하면 source/main/generated
에 생성된다.
Q클래스는 개발자가 수정하거나 생성하는 파일이 아니기 때문에 나는 build/generated
에 생성되도록 Gradle 로 그대로 두었다.
그리고 build task를 실행하면, 아래 위치에 Entity 클래스와 매핑되는 Q클래스가 생성된 것을 볼 수 있다.
annotationProcessor 가 생성해준 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 를 사용하는 구현체를 만들텐데, 그 전에 다이어그램을 쑥 훑어보고 가자!
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
레퍼런스
https://madplay.github.io/post/introduction-to-querydsl
https://jojoldu.tistory.com/372
http://honeymon.io/tech/2020/07/09/gradle-annotation-processor-with-querydsl.html
[JPA] Querydsl 시작하기
글 읽어주셔서 언제나 감사합니다. 좋은 피드백, 개선 피드백 너무나도 환영합니다.
'SearchDeveloper > SpringBoot' 카테고리의 다른 글
[JPA] QueryDSL stream DB connection 에러 해결 과정 ① (0) | 2023.01.02 |
---|---|
[JPA] Multi Datasource 설정하기 (1) | 2022.11.06 |
[JPA] Querydsl 에서 Fetch Join 적용 안되는 이유 (3) | 2022.10.12 |
Spring REST Docs 이해 (2) | 2022.09.24 |
스프링부트 jar 구성 및 실행 원리 (4) | 2022.09.08 |