실전! Querydsl 정리
Intro
안녕하세요? 곽지용 사원입니다.
김영한씨의 ‘실전! Querydsl 정리’ 강의를 듣고나서
꼭 필요했던 부분이나 알아두면 좋은 부분을 정리해보았습니다.
[참고]
강의는 MacOS, IntelliJ 환경이지만
글작성자는 Windows, VSCode 환경에서 따라해보았습니다.https://www.inflearn.com/course/Querydsl-실전
1. 프로젝트 환경설정
1-1. 프로젝트 생성
- 스프링 부트 스타터(https://start.spring.io/) 에서 간단하게 생성 가능합니다.
- SpringBootVersion: 2.2.2 //현재 설정 불가 *2022-01 기준 2.6.2 버전이 최신
- groupId: study
- artifactId: data-jpa
- dependency : Spring Web, jpa, h2, lombok
스프링 부트 스타터에서 추가할 수 있는 dependency 중에 Querydsl이 없습니다.
때문에 추가 설정이 필요합니다.
- 부트 2.2.2버전 Querydsl 추가 설정
- build.gradle
plugins { id 'org.springframework.boot' version ‘2.2.2.RELEASE' id 'io.spring.dependency-management' version '1.0.8.RELEASE' //querydsl 추가 id "com.ewerk.gradle.plugins.querydsl" version "1.0.10" id 'java' } group = 'study' version = '0.0.1-SNAPSHOT' sourceCompatibility = '1.8' 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' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation('org.springframework.boot:spring-boot-starter-test') { exclude group: ‘org.junit.vintage’, module: ‘junit-vintage-engine' } } 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 추가 끝
현재는 버전이 안맞아서 위 방법대로는 적용이 되지않았습니다.
-
2.6.2버전기준 Querydsl 설정
//querydsl 추가 buildscript { dependencies { classpath("gradle.plugin.com.ewerk.gradle.plugins:querydsl-plugin:1.0.10") } } plugins { id 'org.springframework.boot' version '2.6.2' id 'io.spring.dependency-management' version '1.0.11.RELEASE' id 'java' } group = 'study' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' //apply plugin: 'io.spring.dependency-management' apply plugin: "com.ewerk.gradle.plugins.querydsl" 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' implementation 'com.querydsl:querydsl-apt' //sql로그 implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.8.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' } test { useJUnitPlatform() } //querydsl 추가 //def querydslDir = 'src/main/generated' def querydslDir = "$buildDir/generated/querydsl" querydsl { library = "com.querydsl:querydsl-apt" jpa = true querydslSourcesDir = querydslDir } sourceSets { main { java { srcDirs = ['src/main/java', querydslDir] } } } compileQuerydsl{ options.annotationProcessorPath = configurations.querydsl } configurations { querydsl.extendsFrom compileClasspath }
1-2. Gradle 빌드
- Gradle IntelliJ 사용법
- Gradle > Tasks > build > clean
- Gradle > Tasks > other > compileQuerydsl
- Gradle VSCode 사용법
- Gradle Extension Pack 플러그인 설치
- 사이드바의 Gradle 선택
- 프로젝트 선택 > Tasks > build > clean
- 프로젝트 선택 > Tasks > other > compileQuerydsl
작성자는 VSCode를 Ide로 사용했습니다.
VSCode 작업영역에 gradlew.bat 파일이 보이도록 폴더를 배치해야 사이드바에 Gradle 버튼이 활성화됩니다.
2. JPQL과 Querydsl 비교
select * from Member Where username = ‘member1’ 을 나타낸 코드입니다.
-
JPQL
@Test public void startJPQL() { 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"); }
-
Querydsl
@Test public void startQuerydsl() { 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"); }
Querydsl은 쿼리가 아니라 코드로만 작성할 수 있습니다.
-
SQL이 코드로만 작성되어 생기는 이점
- JPQL은 어플리케이션 로딩시점에 에러를 잡을 수 있는게 장점이었다면 Querydsl은 컴파일 시점에서 문법 오류를 잡을 수 있습니다.
- 메서드 체이닝으로 이루어져있기 때문에 조립하기 쉽게 되어있고 당연히 동적쿼리 작성에 유리합니다.
- 자바코드의 장점을 그대로 가져옵니다. (중복코드를 재사용, 자동완성 등)
결론적으로 생산성은 늘고, 에러체크는 더 빨라집니다.
3. 동적쿼리
자바 코드로 구성되어 있기 때문에 동적 쿼리도 간단해집니다.
3-1. BooleanBuilder
private List<Member> searchMember1(String usernameCond, Integer ageCond) {
BooleanBuilder builder = new BooleanBuilder();
if (usernameCond != null) {
builder.and(member.username.eq(usernameCond));
}
if (ageCond != null) {
builder.and(member.age.eq(ageCond));
}
return queryFactory
.selectFrom(member)
.where(builder)
.fetch();
}
3-2. Where 다중 파라미터 사용
private List<Member> searchMember2(String usernameCond, Integer ageCond) {
return queryFactory
.selectFrom(member)
.where(usernameEq(usernameCond), ageEq(ageCond))
.fetch();
}
private BooleanExpression usernameEq(String usernameCond) {
return usernameCond != null ? member.username.eq(usernameCond) : null;
}
private BooleanExpression ageEq(Integer ageCond) {
return ageCond != null ? member.age.eq(ageCond) : null;
}
QueryFactory의 Where 메소드에는 파라미터로 Predicate(서술어) 타입을 받습니다.
BooleanBuilder와 BooleanExpression은 Predicate 인터페이스의 구현체입니다.
4. 서브 쿼리
JPAExpressions 클래스를 이용해서 서브쿼리를 구현가능합니다.
-
Where 서브쿼리
QMember memberSub = new QMember("memberSub"); List<Member> result = queryFactory .selectFrom(member) .where(member.age.eq( JPAExpressions .select(memberSub.age.max()) .from(memberSub) ) ) .fetch();
-
Select 서브쿼리
List<Tuple> fetch = queryFactory .select(member.username, JPAExpressions .select(memberSub.age.avg()) .from(memberSub) ).from(member) .fetch();
from절의 서브쿼리는 JPA에서 지원하지않기 때문에 Querydsl도 사용불가능합니다.
필요한경우 조인으로 해결하거나 안된다면 네이티브 쿼리를 사용해야 합니다.
5. Case문
-
일반적인 case문 Q엔티티에 바로 접근해서 사용
List<String> result = queryFactory .select(member.age .when(10).then("열살") .when(20).then("스무살") .otherwise("기타") ) .from(member) .fetch();
-
CaseBuilder를 이용한 Case문
NumberExpression<Integer> rankPath = new CaseBuilder() .when(member.age.between(0, 20)).then(2) .when(member.age.between(21, 30)).then(1) .otherwise(3); List<Tuple> result = queryFactory .select(member.username, member.age, rankPath) .from(member) .orderBy(rankPath.desc()) .fetch();
CaseBuilder를 이용해 BooleanBuilder처럼 재사용가능한 코드로 만들 수 있다.
6. Expressions
Expressions 클래스는 ANSI 표준의 다양한 함수와 표현식을 지원합니다.
(String, Number, Enum, Constant, Date 등등)
기존 sql 표현식때문에 쿼리를 사용할 필요가 없어졌습니다.
- stringTemplate 예제
Tuple result = queryFactory .select( Expressions.stringTemplate("function('replace', {0}, {1}, {2})", member.username, "member", "M"), Expressions.stringTemplate("DATE_FORMAT({0}, {1})", member.startTime, "%Y-%m-%d").as("date") ) .from(member) .fetchFirst();
7. QuerydslRepositorySupport
스프링 데이터가 제공하는 페이징을 Querydsl로 편리하게 운용하도록 도와줍니다.
-
getQuerydsl().applyPagination()사용 예제
JPQLQuery<StoreVo> query = jpaQueryFactory .select(Projections.fields(StoreVo.class, store.id , store.name , store.address )) .from(store) .where(store.name.eq(name)); long totalCount = query.fetchCount(); List<StoreVo> results = getQuerydsl().applyPagination(pageable, query).fetch();
문제는 해당 기능으로 페이징시 스프링 데이터 Sort 기능이 정상 동작하지 않습니다.
정렬이 필요할 땐 직접 구현해야합니다.
Leave a comment