본문 바로가기
DataBase

jOOQ !! (Java Object Oriented Querying)

by Kingbbode 2017. 6. 27.

 

사내에서 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의 비교가 될 것이고, 아마 관점과 호불호의 차이로 답을 내리기 힘들거라고 생각하여..

nanj

이 부분에 대한 내용은 관련 링크로 대신합니다.

jOOQ vs. Hibernate: When to Choose Which (이 글은 ORM과 SQL에 대한 선택을 다룬 글 입니다.), QueryDSL vs jOOQ (jOOQ 개발자가 QueryDSL의 완성을 축하하면서 그래도 우리가 더 좋다를 써놓은 것 같은 글 입니다.)

두 글 다 작성자가 jOOQ 개발자입니다;;;;


jOOQ 맛보기 ( With Spring Boot )

프로젝트 세팅

Spring Initializer에서 보면 jOOQ를 지원하는 것을 볼 수 있습니다.

init

그리고 지원되는 것이 무려 boot starter! 입니다. 간편하게 세팅이 끝날 것 같은 느낌이 듭니다.

starter

Common application properties를 찾아보면, 필요한 properties도 SQLDialect뿐이 없습니다.

jOOQ 자동화 설정 클래스인 org.springframework.boot.autoconfigure.jooq.JooqAutoConfiguration 클래스를 들여다보면 데이터베이스에 대한 다양한 설정들이 보이고, 가장 핵심인 DSL API를 제공해주는 DslContext Bean 생성에 관한 내용도 보입니다.

auto

특별히 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를 생성시킵니다.

gene

보면 알 수 있듯 생성되는 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들은 유형 안정성을 갖습니다.

typesafe

(빨간줄을 보여주기 위하여 캡처로..)

POSTS의 ID는 Integer Type이기 때문에 Integer와 String의 비교는 허용되지 않습니다.

마찬가지로, 서브쿼리에서도 유형이 맞지 않으면 컴파일 되지 않습니다.

dsl
  .insertInto(POSTS, POSTS.NAME, POSTS.ID)
  .values(3, "kingbbode") <-- ERROR
  .execute();

위의 경우에도 String이 들어갈 곳에 Integer가 들어갔고, Integer가 들어갈 곳에 String이 들어갔기 때문에 컴파일되지 않습니다.

순서가 보장되면서 유형 안정성을 잘 보여줍니다.


컴파일 단계에서 확인

DSL을 통해 쿼리가 작성되기 때문에 문법적인 오류도 컴파일 단계에서 확인이 가능합니다.

misstake

(빨간줄을 보여주기 위하여 캡처로..)

빈 Where절, Integer 하나의 필드만 넘어가야할 서브쿼리에 두 개 필드 등 쿼리가 길어지거나, 정신이 없거나 등등 여러 경우에서 실수가 발생할 수 있는 부분을 확인할 수 있습니다.

dsl
  .insertInto(POSTS, POSTS.NAME, POSTS.ID)
  .values("kingbbode") <-- ERROR
  .execute();

Insert의 경우 values 값의 누락을 컴파일 단계에서 확인할 수 있습니다.


마무리

겉핥기 식으로 정말 간단하게 jOOQ를 정리해보았습니다.

현재 jOOQ로 개발을 시작한지 얼마 되지 않았고, 큰 작업도 해보진 못했습니다. 그래도 왠지 재미있습니다. 이전에는 ORM이 그냥 최고인 줄 알았는데, jOOQ를 조사하다보니 여러가지 경우를 따져가며 무엇을 선택할지를 보는 시야도 중요하다고 느껴집니다.

choose

앞으로 더 재밌어질 것 같습니다. 더 많은 것을 하게 되면 또 글을 올리겠습니다.

댓글