사내에서 Query Repository로 사용하게 된 jOOQ
를 간단하게 정리해보겠습니다.
jOOQ가 뭐지?
Java Object Oriented Querying
jOOQ
는 자바 코드로 쿼리를 작성할 수 있는 데이터베이스 인터페이스
입니다.
데이터베이스 스키마에서 생성 된 클래스의 쿼리를 작성하는 내부 도메인 특정 언어
를 제공하며 내부 도메인 특정 언어
로 SQL을 구현하므로 임의의 복잡성을 지닌 SQL문을 형식에 맞게 구성하고 실행할 수 있습니다.
내가 생각하는 jOOQ
단점도 분명 있겠지만, 조금이나마 사용해본 후 jOOQ
가 좋다고 느낀 이유는 아래와 같습니다.
쿼리를 자바로 짤 수 있다 !
Spring Boot를 사용하면서도 자바 기반의 Config가 정말 좋았습니다. QueryDSL을 접했을 때도 마찬가지..! 제 주언어는 자바이기에 자바로 작성하는게 가장 편하고 좋습니다.
Type Safe를 선호
컴파일 단계에서 Type 매칭에 대한 에러를 확인할 수 있고 Code-assistant를 통해 편하게 개발할 수 있을 것 입니다.
컴파일 단계에서 쿼리의 구문 오류가 확인 가능
Type Safe 외에도 DSL을 통해 자바 코드로 쿼리를 작성하여 기본적인 쿼리 구문 오류를 컴파일 단계에서 확인할 수 있습니다.
Code Generator 지원
데이터베이스를 스캔하여 tables, records, sequences, POJOs, DAOs, stored procedures, user-defined types
등 class들을 생성할 수 있습니다. 쿼리 수행에 필요한 대부분이 노가다없이 제공이 됩니다.
QueryDSL과 비슷한데..
jOOQ는
자바 언어
로 SQL을 보다 잘 통합해주기 위한 도구입니다. 자바 언어로 쿼리를 작성할 수 있다는 것부터 굉장히 매력적입니다.
DSL
을 사용하는 다른 도구인 QueryDSL
이 생각납니다. QueryDSL
과 jOOQ
의 비교는 더 상위 수준으로 올라가 ORM
과 SQL
의 비교가 될 것이고, 아마 관점과 호불호의 차이로 답을 내리기 힘들거라고 생각하여..
이 부분에 대한 내용은 관련 링크로 대신합니다.
jOOQ vs. Hibernate: When to Choose Which (이 글은 ORM과 SQL에 대한 선택을 다룬 글 입니다.), QueryDSL vs jOOQ (jOOQ 개발자가 QueryDSL
의 완성을 축하하면서 그래도 우리가 더 좋다를 써놓은 것 같은 글 입니다.)
두 글 다 작성자가 jOOQ 개발자입니다;;;;
jOOQ 맛보기 ( With Spring Boot )
프로젝트 세팅
Spring Initializer에서 보면 jOOQ를 지원하는 것을 볼 수 있습니다.
그리고 지원되는 것이 무려 boot starter
! 입니다. 간편하게 세팅이 끝날 것 같은 느낌이 듭니다.
Common application properties를 찾아보면, 필요한 properties도 SQLDialect
뿐이 없습니다.
jOOQ 자동화 설정 클래스인 org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration
클래스를 들여다보면 데이터베이스에 대한 다양한 설정들이 보이고, 가장 핵심인 DSL API를 제공해주는 DslContext
Bean 생성에 관한 내용도 보입니다.
특별히 Customize할 부분이 없다면, Boot가 다 해주니 jOOQ 사용을 위한 프로젝트 세팅이 끝난 것으로 보입니다. !!
jOOQ Class 생성
jOOQ는 jOOQ를 편하게 사용하기 위한 CodeGen
을 제공합니다. 그리고 이 CodeGen
을 사용하기 쉽게 만들어놓은 gradle-jooq-plugin이 있습니다. jOOQ 공식 홈페이지에서도 이 플러그인 사용을 권장하고 있습니다.
자세한 사용법은 해당 플러그인 GitHub을 참고!
이 플러그인이 하는 일은 jooq
라는 extention을 통해 값을 셋팅받고 값을 가공하여 Jooq CodeGen으로 넘기는 일을 수행합니다.
세팅되는 값 중 문서에 설명이 충분치 않은 몇 가지만 설명하겠습니다.
jooq {
...
sample(sourceSets.main) {
jdbc {
...
}
generator {
..
strategy {
name = 'org.jooq.util.DefaultGeneratorStrategy'
..
}
database {
...
}
generate {
relations = true
deprecated = false
records = true
immutablePojos = true
fluentSetters = true
pojos = true
// ...
}
target {
packageName = 'nu.studer.sample'
directory = 'jooq'
}
}
}
}
sample
이라고 표기된 부분
이 부분에는 데이터베이스의 이름을 작성할 수 있으며, 아래와 같이 여러 데이터베이스를 작성할 수도 있습니다.
jooq {
...
db1(sourceSets.main) {
...
}
db2(sourceSets.main) {
...
}
db3(sourceSets.main) {
...
}
}
그리고 하나의 데이터베이스는 하나의 Gradle Task를 생성시킵니다.
보면 알 수 있듯 생성되는 Gradle Task는 generate[NAME]JooqSchemaSource
라는 형식으로 생성이 됩니다.
Generator Strategy
말 그대로 생성 전략은 데이터베이스 스키마를 Class화 할 때 어떤 형태로 생성할지에 대한 전략을 나타냅니다.
메서드 이름을 어떻게 할지, 클래스 이름을 어떻게 할지 등 다양한 상황에 대한 전략이 이곳에 명세되어 있습니다. DefaultGeneratorStrategy 클래스를 보면 상속하여 Customize할 수 있게 모두 열려 있습니다.
Jooq에서 Customize Sample로 제공하는 JPrefixGeneratorStrategy
는 모든 클래스명 앞에 J를 붙이는 일을 해주는 Customize Strategy 입니다.
public class JPrefixGeneratorStrategy extends DefaultGeneratorStrategy {
@Override
public String getJavaClassName(final Definition definition, final Mode mode) {
return 'J' + super.getJavaClassName(definition, mode);
}
}
generate 와 target
generate
는 실제 DSL
에서 사용될 수 있는 Table, Record 등과 POJO 객체, Enum 등 여러가지 클래스에 대한 생성 옵션을 줄 수 있습니다. 모든게 다 필요하진 않으니 필요한 클래스만 생성할 수 있습니다.
target
에는 생성되는 클래스들이 생성될 위치(directory)와 그 클래스들이 갖게 될 패키지를 설정할 수 있습니다. 임의의 패키지로 지정되어 버리면 사용할 클래스를 실제 프로젝트 패키지로 옮길 때 불편했었는데 편리한 기능인 것 같습니다.
generate {
relations = true
deprecated = true
instanceFields = true
generatedAnnotation = true
records = true
pojos = true
immutablePojos = false
interfaces = false
daos = false
jpaAnnotations = false
validationAnnotations = false
globalObjectReferences = true
fluentSetters = false
}
target {
packageName = "com.kingbbode.entities"
directory = "jooq/generated"
}
jooqRuntime
gradle-jooq-plugin은 코드 생성 runtime scope를 jooqRumtime
이란 runtime scope로 생성하여 사용하고 있습니다.
jooqRuntime = project.configurations.create("jooqRuntime")
jooqRuntime.setDescription("The classpath used to invoke the jOOQ generator. Add your JDBC drivers or generator extensions here.")
project.dependencies.add(jooqRuntime.name, "org.jooq:jooq-codegen")
기본으로 jooq-codegen
을 jooqRuntime
에 포함시키고 있습니다. 이 jooqRuntime은 build.gralde
에서 작성하게 되는데, 항상 꼭 작성되어야 하는 내용은 데이터베이스 connector 들 입니다.
dependencies {
...
//H2일 때
jooqRuntime 'com.h2database:h2:1.4.193'
//MySQL일 때
jooqRuntime 'mysql:mysql-connector-java:5.+'
...
}
위에서 설명한 Customize Strategy로 사용할 Class가 있다면 jooqRuntime
에 포함시켜야 합니다.
jooqRuntime project(':custom-generator') // include sub-project that contains the custom generator strategy
플러그인 사용법을 해당 플러그인 GitHub을 참고하여 jOOQ Class를 뽑아냅니다.
DSL
jOOQ 사용 관련 내용은 아주 많기때문에, 저는 TypeSafe와 컴파일 단계에서의 구문 오류 정도만 살피려고 합니다.
jOOQ 사용에 대한 Tutorial은 이 링크를 추천드립니다.
대략 적인 사용성만!(select join)
dsl .select(CONTENT.IDX, CONTENT.BODY, CATEGORY.IDX.as("categoryIdx"), CATEGORY.NAME.as("categoryName")) .from(CONTENT) .join(CATEGORY).on(CATEGORY.IDX.eq(CONTENT.CATEGORY_IDX)) .offset(offset) .limit(limit) .fetch() .into(ContentDetailResponse.class);
TypeSafe
jOOQ로부터 생성된 Class들은 유형 안정성을 갖습니다.
(빨간줄을 보여주기 위하여 캡처로..)
POSTS의 ID는 Integer Type이기 때문에 Integer와 String의 비교는 허용되지 않습니다.
마찬가지로, 서브쿼리에서도 유형이 맞지 않으면 컴파일 되지 않습니다.
dsl
.insertInto(POSTS, POSTS.NAME, POSTS.ID)
.values(3, "kingbbode") <-- ERROR
.execute();
위의 경우에도 String이 들어갈 곳에 Integer가 들어갔고, Integer가 들어갈 곳에 String이 들어갔기 때문에 컴파일되지 않습니다.
순서가 보장되면서 유형 안정성을 잘 보여줍니다.
컴파일 단계에서 확인
DSL을 통해 쿼리가 작성되기 때문에 문법적인 오류도 컴파일 단계에서 확인이 가능합니다.
(빨간줄을 보여주기 위하여 캡처로..)
빈 Where절, Integer 하나의 필드만 넘어가야할 서브쿼리에 두 개 필드 등 쿼리가 길어지거나, 정신이 없거나 등등 여러 경우에서 실수가 발생할 수 있는 부분을 확인할 수 있습니다.
dsl
.insertInto(POSTS, POSTS.NAME, POSTS.ID)
.values("kingbbode") <-- ERROR
.execute();
Insert의 경우 values 값의 누락을 컴파일 단계에서 확인할 수 있습니다.
마무리
겉핥기 식으로 정말 간단하게 jOOQ를 정리해보았습니다.
현재 jOOQ로 개발을 시작한지 얼마 되지 않았고, 큰 작업도 해보진 못했습니다. 그래도 왠지 재미있습니다. 이전에는 ORM이 그냥 최고인 줄 알았는데, jOOQ를 조사하다보니 여러가지 경우를 따져가며 무엇을 선택할지를 보는 시야도 중요하다고 느껴집니다.
앞으로 더 재밌어질 것 같습니다. 더 많은 것을 하게 되면 또 글을 올리겠습니다.
댓글