티스토리 뷰
개요
이직 후 거의 바로 QueryDSL을 도입했었다.
스프링부트에 QueryDSL 적용기 - 1 (Mybatis vs JPA vs JOOQ vs QueryDSL 비교)
스프링부트에 QueryDSL 적용기 - 2 (설치 및 사용법, Spring Data JPA와 함께 사용하기)
QueryDSL 도입에는 여러가지 이유가 있는데 대표적인 이유로는 담당하게된 프로젝트가 MyBatis와 JPA가 혼재되어 있었다는게 첫번째 이유였다. JPA를 완전히 덜어내기 어려운 상황이었고, 두번째는 이직 전 회사에서부터 엄청난 길이의 MyBatis 동적쿼리에 고통받고 있던 나는 Native Query를 백엔드 코드에서 더 이상 보고싶지 않았기 때문이었다.
QueryDSL 도입 초기에는 컴파일 단계에서 쿼리 오류를 잡아주는 게 너무 좋았는데.... 결국 QueryDSL은 JPA가 갖고 있는 문제점과 더불어 또다른 문제를 갖고 있었다.
내가 느낀 JPA가 가진 문제들
1. N+1과 같은 유명한 문제
2. Entity 객체를 매번 생성해줘야 함(귀찮음)
3. 테이블 연관관계 표현을 잘하기 어려움
4. 복잡한 테이블의 연계를 하거나 고성능의 쿼리를 짜려면 결국 Native 쿼리를 써야함
내가 느낀 QueryDSL의 문제점들
1. JPQL 기반이라 Projections을 이용할 수 밖에 없는 객체 매핑 방법
2. DB 방언 사용하려면 어쩔 수 없이 native Query를 사용해야 함
3. from절에 서브쿼리를 쓸 수 없고, alias 핸들링이 어려움. JPAExpressions가 생각처럼 안되는 경우가 종종 있음
4. 동적인 정렬이 이상하게 동작하지 않는 경우가 많음. OrderSpecifier도 생각처럼 동작하지 않음.
5. Q파일로 인한 빌드 속도 저하
JPAExpressions 와 OrderSpecifier는 내가 잘 사용하지 못한 이유도 있었겠지만, 이 두 개를 계속 사용하면서 쿼리랑 너무 멀어진다는 느낌을 지울 수 없었다(단순 서브쿼리와 조인 절들이 별도의 매서드로 계속 빠져나옴). DSL이니 어쩔 수 없긴 하겠지만, 결국 쿼리 동작 테스트는 DB에서 하고 오기 때문에 여기서 너무 큰 괴리가 생기기 시작했다.
난 SQL 쿼리에 익숙하지 DSL에 익숙한게 아니었다.
그리고 JPA에서 자주 발생하는 복잡한 쿼리에 따른 테이블 연계 및 상관 관계 문제(대표적으로 N+1 문제가 있겠다)에 대해서 김영한 선생님도 강의에서 가볍게 언급한 적이있는데, 이 때는 그냥 쿼리를 잘 나눠서 구현하면 된다고 말씀해주셨다. 그러나 실제 개발을 하다보면 그렇지 못하는 상황들이 많았다.
이처럼 실무에서 발생한 여러 문제 때문에 다른 방법을 찾아다니던 중 JOOQ를 강력 추천을 받았고, 이번 프로젝트에 적용해보기로 했다.
그러나 문제는 QueryDSL 적용기에서도 작성했다시피 설정 방법이 너무 복잡했다... 거기다 GradleDSL이 사례가 많아서 Kotlin DSL 사례는 찾기가 힘들었지만 어찌어찌 해냈다.
개요가 너무 길었는데... 일단 스프링부트 프로젝트에 적용해보자.
DB는 PostgresSQL를 썼고, SpringBoot 버전은 3.4.0을 썼다.
JOOQ 설정하기
gradle
plugins {
kotlin("jvm") version "1.9.0"
kotlin("plugin.spring") version "1.9.0"
id("org.springframework.boot") version "3.4.0"
id("io.spring.dependency-management") version "1.1.3"
id("nu.studer.jooq") version "9.0"
}
...
dependencies {
implementation("org.springframework.boot:spring-boot-starter-jooq")
implementation("org.jooq:jooq:3.19.5")
implementation("org.jooq:jooq-codegen:3.19.5")
jooqGenerator("org.postgresql:postgresql:42.7.2")
jooqGenerator("org.jooq:jooq-meta:3.19.5")
jooqGenerator("org.jooq:jooq-codegen:3.19.5")
runtimeOnly("org.postgresql:postgresql")
}
...
jooq {
configurations {
create("main") {
generateSchemaSourceOnCompilation.set(false)
jooqConfiguration.apply {
jdbc.apply {
driver = "org.postgresql.Driver"
url = "[DB URL]"
user = "[USER_ID]"
password = "[PASSWORD]"
}
generator.apply {
name = "org.jooq.codegen.KotlinGenerator"
database.apply {
name = "org.jooq.meta.postgres.PostgresDatabase"
inputSchema = "public"
forcedTypes = listOf(
ForcedType().apply {
userType = "java.lang.Long"
includeTypes = "bigint"
},
ForcedType().apply {
userType = "java.lang.Integer"
includeTypes = "integer"
},
ForcedType().apply {
userType = "java.lang.Boolean"
includeTypes = "boolean"
}
)
}
generate.apply {
isDaos = true
isRecords = true
isFluentSetters = true
isJavaTimeTypes = true
isDeprecated = false
}
target.apply {
directory = "src/generated"
}
strategy.name = "org.jooq.codegen.DefaultGeneratorStrategy"
}
}
}
}
}
KotlinDSL로 한번에 정리된 문서를 찾기 힘들어서 여러 레퍼런스를 찾아다녔다. 그러던 중 강의를 만드신 분에게 도움을 받게되서 어떻게 시작을 해볼 수 있게 됐다. https://sightstudio.tistory.com/73
팀원의 학습곡선이 적댔지, 도입하는 사람의 학습곡선이 적다곤 하지 않았다.
이 말이 너무 와닿았다. 이렇게 설정하고 gradle을 갱신하면 gradle 설정에서 jooq 아래에 generateJooq가 생성된다.
generateJooq를 실행시키면 실제 DB에 접근해서 테이블 정보를 가져오고, 자동으로 테이블에 매핑된 객체를 생성해준다.
생성된 파일의 경로는 src/generated 아래다.
다음은 간단한 쿼리를 만들어보자.
// Greeting은 id와 message 두개의 컬럼을 갖는 테이블이다.
@Repository
class GreetingJooqRepository(private val dslContext: DSLContext) {
fun selectOneById(id: Long): GreetingRecord {
val jGreeting = Greeting.GREETING
return dslContext
.selectFrom(jGreeting)
.where(jGreeting.ID.eq(id))
.fetchOneInto(GreetingRecord::class.java)
?: throw NoSuchElementException("Greeting with ID $id not found")
}
}
여기서는 자동으로 만들어주는 Record 객체를 사용했지만, 내가 사용하려는 객체에 더 잘 매핑해줄 수 있다.
JPA의 Projections보다 훨씬 편한 방법으로 처리할 수 있으니 다음에는 이 방법들과 기본 쿼리 처리방법에 대해서 정리해 보려고 한다.
마치며
내가 JPA + QueryDSL의 100%를 뽑아내지 못했기 때문에 느꼈던 문제였다고도 생각하긴한다.
그러나 위에서도 언급했지만 QueryDSL은 결국 DSL이기 때문에 오는 실제 쿼리와의 괴리가 점점 크게 느껴졌다.
쿼리를 짜고 DSL로 변경할 때마다, 이럴바엔 그냥 다 JPQL native 쿼리 바꾸는게 편할거같다는 생각을 정말 많이했었다.
아직 JOOQ도 많이 써보진 않았지만, QueryDSL보다는 쿼리에 가깝다는 느낌을 받았다.
조금 더 쓰다보면 다른 생각이 들지는 아직 알 수 없다.
추가적으로 한가지 해보고 싶은건 JOOQ와 JPA의 통합인데.. 이건 좀 난이도가 있다.
다음은 CRUD 쿼리 사용법에 대해 정리해보려고 한다.
'개발 > SPRING' 카테고리의 다른 글
스프링부트에서 무조건 한번 실행하게 하기 (0) | 2025.03.07 |
---|---|
테스트를 도입하면서 느낀점과 고민들 (0) | 2024.08.14 |
SpringBoot에서 MultipartFile 전송하기 feat. FeignClient (1) | 2024.07.23 |
Kotlin + SpringBoot 서버에 테스트 도입기. Kotest와 MockK (0) | 2024.07.19 |
Layered 아키텍처에서 Hexagonal 아키텍처로 리팩토링하면서 느낀 점 (0) | 2024.07.04 |
- Total
- Today
- Yesterday
- springboot
- ChatGPT
- terraform
- AWS
- CloudFront
- serverless
- java
- docker
- OpenFeign
- 람다
- AOP
- MySQL
- AWS EC2
- S3
- 후쿠오카
- Kotlin
- GIT
- elasticsearch
- OpenAI
- Log
- 티스토리챌린지
- Spring
- cache
- openAI API
- Elastic cloud
- lambda
- EKS
- JWT
- 스프링부트
- 오블완
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |