여차여차하다보니, 드뎌 QueryDSL를 직접 다뤄볼 수 있는 기회까지 오게되었다.
QueryDSL은 Java로 SQL과 같은 쿼리를 타입-세이프
하게 작성할 수 있게 도와주는 프레임워크 이다.
물론 적용할 수 있는 곳은 JPA이며, 아쉽게도 start.spring.io 에서 dependency로 추가할 수 없고, 직접 build.gradle에 추가해줘야한다.
Querydsl공식사이트
Querydsl - JPA 튜토리얼 가이드
other ->compileQuerydsl
이 생성된다.plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}
group = 'study'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//test 롬복 사용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.6.2'
}
tasks.named('test') {
useJUnitPlatform()
}
clean {
delete file('src/main/generated')
}
def querydslDir = "$projectDir/build/generated/querydsl"
querydsl {
jpa = true
querydslSourcesDir = querydslDir
}
sourceSets {
main.java.srcDir querydslDir
}
configurations {
querydsl.extendsFrom compileClasspath
}
compileQuerydsl {
options.annotationProcessorPath = configurations.querydsl
}
id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
을 꼭 추가해줘야 한다.build ->build
만 실행해도 모두 적용되 실행되기 때문이다.plugins {
id 'java'
id 'org.springframework.boot' version '3.2.2'
id 'io.spring.dependency-management' version '1.1.4'
// 플러그인 없어도 됨.
}
// 동일한 부분 생략
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.h2database:h2'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
//test 롬복 사용
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
//Querydsl 추가
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.6.2'
}
// 추가된 부분
sourceSets { main { java { srcDirs = ["projectDir/src/main/java", "projectDir/build/generated"] } } }
@Test
public void startQuerydsl() {
//given
//JPAQueryFactory jpaQueryFactory = new JPAQueryFactory(em);
// QMember member = QMember.member;
// QMember m = new QMember("m");
//when
Member findMember = jpaQueryFactory
.select(member)
.from(member)
.where(
member.username.eq("member1")
) //파라미터 바인딩 처리
.fetchOne();
//then
assertThat(findMember.getUsername()).isEqualTo("member1");
}
member.username.eq("member1") // username = 'member1'
member.username.eq("member1").not() // username != 'member1'
member.username.isNotNull() //이름이 is not null
member.age.in(10, 20) // age in (10,20)
member.age.notIn(10, 20) // age not in (10, 20)
member.age.between(10,30) //between 10, 30
member.age.goe(30) // age >= 30
member.age.gt(30) // age > 30
member.age.loe(30) // age <= 30
member.age.lt(30) // age < 30
member.username.like("member%") //like 검색
member.username.contains("member") // like ‘%member%’ 검색
member.username.startsWith("member") //like ‘member%’ 검색
fetch()
: 리스트 조회, 데이터 없으면 빈 리스트 반환
fetchOne()
: 단 건 조회
null
com.querydsl.core.NonUniqueResultException
fetchFirst()
: 맨 처음 1 건 조회
fetchResults()
: 페이징 정보 포함, total count 쿼리 추가 실행
fetchCount()
: count 쿼리로 변경해서 count 수 조회
orderBy(member.age.desc(), member.username.asc().nullsLast())
: 정렬 나이 내림차순1순위, 이름 올림차순 2순위 null일 경우 마지막으로 정렬
nullsFirst()
: null일 경우 처음으로 정렬.fetchResults()
: fetch()
의 경우 반환값이 List<?>
이지만, 이건 QueryResults<?>
이며, 아래 getTotal값을 구하기위해 count()쿼리를 한번더 보내는 작업이 생긴다.
queryResults.getTotal()); // 전체 조회
queryResults.getLimit()); // 조회제한한 갯수
queryResults.getOffset());// offset 한 인덱스
queryResults.getResults(); // list 결과
그룹된 상태에서 사용할 수 있는 집계함수도 사용할 수 있다.
당연히 GroupBy도 사용 가능하다.
List<Tuple> result = queryFactory
.select(team.name, member.age.avg())
.from(member)
.join(member.team, team)
.groupBy(team.name)
// .having()
.fetch();
여기까지 하면서 느낀것은 SQL 문법을 할줄 알면, QueryDSL도 직관적으로 금방 습득할만하다는 것이다.
(포스팅이 너무 길어지니 여기서 끊고가고자한다.)
다음엔 SQL 쿼리에서 중요한 Join하는 법과 서브쿼리를 숙지하고 동적쿼리를 어떻게 만드는지 알아보고자 한다.