쿼리를 자바 코드로 작성할 수 있게 도와주는 기술.
정적 타입을 이용해서 SQL과 같은 쿼리를 생성할 수 있도록 해 주는 프레임워크이다.
문자열로 작성하거나 XML 파일에 쿼리를 작성하는 대신, QueryDSL
이 제공하는 플루언트(Fluent) API를 이용해서 쿼리를 생성할 수 있다.
단순 문자열과 비교해서 Fluent API를 사용할 때 다음과 같은 장점이 있다.
1. IDE의 코드 자동 완성 기능 사용
2. 문법적으로 잘못된 쿼리를 허용하지 않음
3. 도메인 타입과 프로퍼티를 안전하게 참조할 수 있음
4. 도메인 타입의 리팩토링을 더 잘 할 수 있음
QueryDSL
은 타입에 안전한 방식으로 HQL 쿼리를 실행하기 위한 목적으로 만들어졌다.
HQL 쿼리를 작성하다보면 String 연결을 이용하게 되고, 이는 결과적으로 코드 가독성을 해친다.
String을 이용해서 도메인 타입과 프로퍼티를 참조하다보면 오타 등으로 잘못된 참조를 하게 될 수 있으며, 이는 String을 이용해서 HQL을 작성할 때의 단점.
Type safety
하도록 도메인 모델을 변경하면 소프트웨어 개발에서 큰 이득을 얻게 된다. 도메인의 변경이 직접적으로 쿼리에 반영되고, 쿼리 작성 과정에서 코드 자동완성 기능을 사용함으로써 쿼리를 더 빠르고 안전하게 만들 수 있게 된다.
타입 안정성(Type safety)
도메인 타입의 프로퍼티를 반영해서 생성한 쿼리 타입을 이용해서 쿼리를 작성하게 된다.
또한, 완전히 타입에 안전한 방법으로 함수/메서드 호출이 이루어진다.
일관성(consistency)
기반 기술에 상관없이 쿼리 경로와 오퍼레이션은 모두 동일하며, Query 인터페이스는 공통의 상위 인터페이스를 갖는다.
모든 쿼리 인스턴스는 여러 차례 재사용 가능하다. 쿼리 실행 이후 페이징 데이터와 프로젝션 정의는 제거된다.
/**
* build.gradle
*/
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"
id 'java'
}
...
dependencies {
...
//querydsl 추가
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}"
...
}
...
//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 추가 끝
Gradle Project → Tasks→other→compileQuerydsl
@Configuration
public class QueryDSLConfig {
@PersistenceContext
private EntityManager entityManager;
@Bean
public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager);
}
}
@SpringBootTest
@Transactional
public class QuerydslBasicTest {
@PersistenceContext
EntityManager em;
@BeforeEach
public void before() {
Team teamA = new Team("teamA");
Team teamB = new Team("teamB");
em.persist(teamA);
em.persist(teamB);
Member member1 = new Member("member1", 10, teamA);
Member member2 = new Member("member2", 20, teamA);
Member member3 = new Member("member3", 30, teamB);
Member member4 = new Member("member4", 40, teamB);
em.persist(member1);
em.persist(member2);
em.persist(member3);
em.persist(member4);
}
}
@Test
public void startJPQL() {
//member1을 찾아라.
String qlString =
"select m from Member m " +
"where m.username = :username";
Member findMember = em.createQuery(qlString, Member.class)
.setParameter("username", "member1")
.getSingleResult();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
@Test
public void startQuerydsl() {
//member1을 찾아라.
JPAQueryFactory queryFactory = new JPAQueryFactory(em);
QMember m = new QMember("m");
Member findMember = queryFactory
.select(m)
.from(m)
.where(m.username.eq("member1"))//파라미터 바인딩 처리
.fetchOne();
assertThat(findMember.getUsername()).isEqualTo("member1");
}
위 테스트 예시에서 확인할 수 있듯이, JPQL은 String(문자)으로 처리하며 QueryDsl은 코드로 처리한다.
문자가 아닌 코드로 처리함으로써 런타임 시점에서 에러가 발생하는 JPQL과 비교해 QueryDSL은 컴파일 시점에서 에러가 발생한다. Type safety
.
또한 JPQL은 파라미터 바인딩을 직접 걸어주지만, QueryDSL은 자동 처리한다.