QueryDSL 사용이유와 사용방법
들어가기에 앞서
실무에서 사용하다보면 복잡한쿼리도 많아지고 동적쿼리도 피치못하게 사용해야하는 경우가 생기기 마련이다. JPQL로도 충분히 문제를 해결할 수는 있지만 복잡하고 손이 많이 갈 수 밖에 없는 형태가 되버린다.
JPQL의 한계를 극복할 수 있는 대안 중 하나가 바로 QueryDSL이다. 단순히 쿼리를 작성하는 것이 아니라 쿼리를 자바코드로서 작성할 수 있게 해줌으로서 많은 것들을 극복하고 훨씬 더 편리하게 사용할 수 있게 되었다.
각자가 사용하는 환경에 따라서 설정방법이 달라질 수 있겠다. 필자가 사용하는 환경은 SpringBoot, Spring Data Jpa, gradle을 사용했다.
QueryDSL을 사용하는 이유
QueryDSL을 사용하면 쿼리를 자바코드로 작성할 수 있게 된다. 처음 이것을 접하는 사람들은 도저히 무슨 이야기인지 이해할 수 없을 수 있지만 자세한 내용은 뒤에서 다뤄보도록 하자.
(1) 자바코드로 작성하게 됨으로서 컴파일 에러로 쿼리의 실수를 잡을 수 있다.
JPQL을 사용하면 쿼리를 문자열에 감싸서 작성해야한다. 문자열은 결국 오타가 나거나 실수로 잘못된 입력을 해도 그것을 실행시키기 전까지는 잘못된 부분을 잡을 수 있는 방법이 없다.
하지만 QueryDSL을 사용하면 자바코드로 작성하기 때문에 실수를 했을 때 컴파일 에러로 모두 잡을 수 있다. 에러중 가장 좋은 에러는 역시 컴파일 에러다.
(2) 복잡한 동적쿼리를 쉽게 다룰 수 있다.
JPQL을 이용해서 동적쿼리를 다루려면 문자열을 조건에 맞게 조합해서 사용해야한다. 물론 이렇게도 사용할 수는 있지만 굉장히 복잡하고 코드도 난해해진다.
문자열로 코드를 작성해야하기 때문에 컴파일 에러도 잡을 수 없는데 복잡하고 난해하기까지 하면 어쩔 수 없이 실수가 나올 수 밖에 없다. 그렇게 발생하는 오타나 잘못된 입력으로 인한 실수는 찾기도 쉽지 않기 때문에 동적쿼리의 문제를 극복하는 것은 굉장히 큰 의미가 있다.
QueryDSL 추가하기
QueryDSL은 기본적으로 QEntity의 형태를 만들어서 사용한다. 우리가 만든 Entity에 대한 내용을 바탕으로 QEntity를 새로 만드는 것이다.
build.gradle에 설정을 추가해주어야 한다.
먼저 QueryDSL버전을 5.0을 사용할 것이다. 맨 위에 버전을 입력해주자.
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
...
다음으로 플러그인을 추가해주자.
plugins {
id 'org.springframework.boot' version '2.7.0'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" //querydsl 추가
id 'java'
}
dependency를 추가해주자.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
//querydsl 추가
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
- jpa : jpa에서 사용하는 QueryDSL의 코어에 해당하는 부분이다. QueryDSL은 JPA뿐만 아니라 SQL, Mongodb, JDO 등 다양한 부분에 맞게 지원하기 때문에 jpa를 명시해준다.
- apt : QEntity를 만드는데 사용된다.
마지막으로 QueryDSL을 추가할 때 사용되는 설정들을 추가하자.
...
//querydsl 추가 시작
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
//querydsl 추가
querydslDir = "$buildDir/generated/querydsl"를 보면 build안에 폴더를 만들어서 생성하도록 하고있다. 이렇게 하면 최상위 폴더에있는 build안에 QueryDSL파일이 생성된다. QueryDSL의 내용은 git에 등록하면 안되는데 build폴더는 기본적으로 .gitignore에 등록되어있기 때문에 따로 등록하지 않아도 git에 등록되지 않는다. 만약 .gitignore에 등록되지 않은 폴더에 QueryDSL파일을 생성하는 경우에는 따로 .gitignore에 등록해주어야 한다.
전체 코드를 보면 다음과 같다.
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
plugins {
id 'org.springframework.boot' version '2.7.0'
id 'io.spring.dependency-management' version '1.0.11.RELEASE'
//querydsl 추가
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
id 'java'
}
group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
//querydsl 추가
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
tasks.named('test') {
useJUnitPlatform()
}
//querydsl 추가 시작
def querydslDir = "$buildDir/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
//querydsl 추가
이제 build.gradle을 새로고침해서 작성한 내용을 적용시켜주자.
QEntity를 만드는 방법은 여러가지가 있는데 오른쪽에 있는 gradle -> other-> complieQuerydsl을 클릭하는 방법도 있지만 프로젝트를 build하는 방법도 있다.
무사히 생성됐다면 다음과 같은 모습이 보일 것이다.
이제 코드로 작성해보면서 사용방법을 알아보자.
QueryDSL 사용하기
필자는 Entity를 Member와 Team을 사용할 것이다. 각각 코드는 다음과 같다.
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id @GeneratedValue
private Long id;
private String username;
private int age;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "team_id")
private Team team;
public Member(String username, int age, Team team) {
this.username = username;
this.age = age;
if (team != null) {
changeTeam(team);
}
}
private void changeTeam(Team team) {
this.team = team;
team.getMembers().add(this);
}
}
@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Team {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<>();
public Team(String name) {
this.name = name;
}
}
이제 테스트코드를 만들어보자.
@SpringBootTest
@Transactional
public class Querydsl {
@Autowired EntityManager em;
@Test
void QueryDSLTest() throws Exception {
Team teamA = new Team("TeamA");
em.persist(teamA);
Member member1 = new Member("member1", 10, teamA);
em.persist(member1);
em.flush();
em.clear();
JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(em); - 1
QMember m = new QMember("m"); - 2
Member DSLMember = jpaQueryFactory - 3
.select(m)
.from(m)
.where(m.username.eq("member1"))
.fetchOne();
Member JPQLMember = em.createQuery("select m from Member m where m.username = :username", Member.class) - 4
.setParameter("username", "member1")
.getSingleResult();
assertThat(DSLMember.getUsername()).isEqualTo("member1");
assertThat(JPQLMember.getUsername()).isEqualTo("member1");
}
}
EntityManager를 가져온 뒤 Team1과 Member1을 생성하였다.
- QueryDSL을 사용하기 위해서는 JPAQueryFactory를 사용해야한다. EntityManager를 파라미터로 넣어서 생성하면 된다.
- 앞서 생성했던 QEntity이다. QueryDSL은 QEntity를 이용해서 동작하기 때문에 항상 QEntity를 생성해서 사용해야 한다.
- 쿼리를 작성하는 단계이다. 우리가 평소에 사용하는 SQL문과 전혀 이질감없이 사용할 수 있지만 모든 형식이 자바코드이다. 자바코드이기 때문에 잘못된 입력에 대해서 컴파일 에러를 일으켜준다.
- QueryDSL과의 비교를 위해 작성한 JPQL이다. 잘 보면 QueryDSL은 JPQL과 달리 파라미터를 따로 바인딩하는 작업이 없다. 위의 코드 처럼 사용해도 자동으로 파라미터를 매핑해서 동작시켜준다.
코드에서 QEntity를 사용하는 것을 볼 수 있다. QEntity를 사용하는 방법은 여러가지가 있다.
- 직접 별칭을 지정해서 사용
- 미리 생성된 것을 가져다 사용
- static import시켜서 사용
(1) 직접 별칭을 지정해서 사용
위에서 사용된 방법이 직접 별칭을 지정해서 사용하는 것이다.
@SpringBootTest
@Transactional
public class Querydsl {
@Autowired EntityManager em;
@Test
void QueryDSLTest() throws Exception {
...
QMember m1 = new QMember("m1");
QMember m2 = new QMember("m2");
...
}
}
처음 QEntity를 생성할 때 원하는 별칭을 입력해서 생성하는 것이다. 만약 같은 테이블 2개를 조인해서 사용해야 하는 경우라면 이렇게 서로 다른 QEntity를 생성해서 사용해야 할 것이다.
(2) 미리 생성된 것을 가져다 사용
QEntity내부를 보면 이미 자동으로 생성된 static 생성자가 존재한다.
이것을 가져다 사용하는 것이다.
@SpringBootTest
@Transactional
public class Querydsl {
@Autowired EntityManager em;
@Test
void QueryDSLTest() throws Exception {
...
QMember m1 = QMember.member;
...
}
}
이렇게 하면 별칭이 이미 지정된 값이기 떄문에 같은 QEntity를 생성하려면 별칭을 직접 입력해서 사용해야 한다.
(3) static import시켜서 사용
import static com.example.querydsl.jpa.domain.QMember.*;
@SpringBootTest
@Transactional
public class Querydsl {
@Autowired EntityManager em;
@Test
void QueryDSLTest() throws Exception {
...
Member DSLMember = jpaQueryFactory
.select(member)
.from(member)
.where(member.username.eq("member1"))
.fetchOne();
...
}
}
static import로 완전히 분리해서 사용할 수도 있다. 이렇게하면 매번 QEntity를 생성해서 사용할 필요 없이 깔끔하게 사용할 수 있다. 가장 많이 권장하는 방법은 이 방법이다.
하지만 역시 같은 QEntity를 생성해야 할 떄는 따로 별칭을 입력해서 생성해주어야 한다.
해당 게시글은 김영한님의 '실전! Querydsl'을 공부하고 작성한 내용입니다.
https://www.inflearn.com/course/Querydsl-%EC%8B%A4%EC%A0%84/dashboard
실전! Querydsl - 인프런 | 강의
Querydsl의 기초부터 실무 활용까지, 한번에 해결해보세요!, - 강의 소개 | 인프런...
www.inflearn.com