티스토리 뷰
신규 프로젝트에 DB access를 어떤 프레임워크를 사용할까 고민을 해봤다. 일단 난 MyBatis만 사용해와서 이번에 다른 프레임워크를 사용해보고싶은 마음이 있었다. 후보군은 이렇다.
- JPA
- JdbcTemplate
- MyBatis
- jooq
아직 실무에서 JPA 경험이 없는터라 JPA를 사용할까도 생각해봤지만 팀 구성원에 JPA에 경험이 있는 사람이 없고, 다른 기술셋들도 생소한걸 사용하기로해서 패러다임 자체를 바꿔야하는 JPA는 좀 무리라고 생각했다. 서비스 구성이 readonly라서 select만 필요한것도 JPA를 욕심내지않는것에 한몫하기도했다. 그러던중 jooq 라는 프레임워크가 있는걸 알게되어 도입을 고민해보게됐다. 각 기술에 대한 결정을 하게된 배경은 이렇다.
- JPA
- JPA에 대한 내용은 위에 작성했다.
- JdbcTemplate
- 일단 이번 프로젝트는 자바가 아니라 코틀린을 이용하기로했기때문에 멀티라인문자열에 대한 거부감도없었고, 굳이 MyBatis를 써야하나? 라는 생각이 들어 진지하게 고민했다. 하지만 여튼 문자열로 쿼리를 작성해야하고 쿼리에 대한 검증을 컴파일 타임에 할수없다는 이유때문에 jooq에 밀렸다.
- MyBatis
- 가장 많이 사용해봤고, 가장 친숙한 프레임워크. 하지만 그렇기때문에 이번엔 좀 배제했다.다른기술도 사용해보고싶었다. 하지만 다른 기술을 사용하다가 이슈가 발생하면 가장 먼저 돌아갈계획으로 잡고있다.
- jooq
- 이리저리 이름만 들어보고 뭔지도 잘 몰랐는데 이번에 도입을 고민하게됐다. 가장 매력적이었던 쿼리를 자바로 작성해서 컴파일 타임에 쿼리를 오타를 검증할 수 있다는 점이다. 말했듯이 난 쭉 MyBatis만 써왔는데 쿼리 오타에 대한 고통을 해결하기위해 이런저런 방법들을 나름 많이 써봤지만 그 해결책 자체가 다른 단점을 내포하고있는 경우도있었기때문에 컴파일타임에 쿼리를 검증할 수 있다는건 매우 매력적으로 보였다. 그리고 spring 에서 공식적으로 jooq starter 를 제공하는것도 영향을 끼쳤다.
JOOQ
이런저런 고민끝에 이번엔 jooq를 도입하게됐다. 일단 이번포스팅은 spring boot에 jooq를 적용하는 간단한 예제정도만 적을 예정이다. 앞으로 시리즈로 포스팅을 이어갈지는 잘 모르겠다.
사용한 기술들은 이렇다.
kotlin
spring boot 2.1.4
jooq 3.11.11
sqlite
언어는 자바가 아니라 코틀린을 사용하기는 했는데 jooq를 적용하는데 있어선 이 포스팅을 작성하는 수준의 코드에선 신경써줘야할것은 없었다.
추가한 의존성들은 아래와 같다.
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
implementation 'org.springframework.boot:spring-boot-starter-jooq'
implementation 'org.xerial:sqlite-jdbc'
아래 쿼리를 jooq로 변경하는걸 살펴보자
jdbcTemplate.queryForList("SELECT * FROM subway_station_facility")
이미 jooq를 추가해놓은 상태이니 다른 의존성을 추가할 필요는 없다. application.yml(혹은 properties)에 sqlite를 사용한다는 점만 설정해주면 된다. oracle이나 mysql 같은 다른 DB를 사용한다면 해당 DB를 명시해줘야한다.
spring:
datasource:
driver-class-name: org.sqlite.JDBC
url: jdbc:sqlite::resource:qsvc.splite3
jooq:
sql-dialect: sqlite
언어가 코틀린인점을 감안하고 코틀린을 전혀 모르더라도 이해하는데 어렵진 않을것이다.
val sql = dslContext.select(field("id"))
.from(table("table"))
.sql
return jdbcTemplate.queryForList(sql)
음.. 뭔가 dsl 스럽게 변경됐다. 컬럼이나 테이블을 문자열로 작성했지만 SQL 자체는 추상화되었기때문에 DBMS가 변경되더라도 스키마만 동일하다면 쿼리는 손을 대지 않아도된다. oracle에서 mysql로 변경되어도 jooq가 알아서 변경해준다. 단 SQL은 추상화됐지만 컬럼과 테이블명은 아직 문자열이다. 이걸로는 자바로 쿼리를 작성하는것에 대해 아무런 이점이 없다. 컬럼이나 테이블명에 오타가 생겨도 (컴파일 타임에) 알 수 없고, 컬럼이나 테이블명이 변경되어도 감지할 수 없다.
Code Generate
jooq는 DB 스키마를 기반으로 자바 클래스를 생성해주는 기능을 제공한다. 이 기능을 이용하면 위 코드를 아래처럼 변경할 수 있다.
val sql = dslContext.select(db.TABLE.ID)
.from(db.TABLE)
.sql
return jdbcTemplate.queryForList(sql)
문자열이 아니라 클래스로 변경됐다. 물론 저 클래스를 직접만들어도 상관없지만 음.. 그러고싶은가? 난 직접 만들고싶지는 않다. 이제 저 클래스를 생성하는 방법을 알아보자.
클래스들을 생성하기위해선 4개의 jar 파일이 있어야한다.
jooq-3.11.11.jar
jooq-meta-3.11.11.jar
jooq-codegen-3.11.11.jar
org.xerial:sqlite-jdbc:3.25.2.jar
jooq관련 3개의 jar와 jdbc connector jar다. sqlite가 아닌 DB를 사용한다면 당연히 저건 바꿔줘야한다.
그리고 코드생성하는것에 대한 설정 xml 파일이 하나 필요하다. 설정 xml 파일은 jooq 공식 홈페이지에서 견본을 가져왔다.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<configuration xmlns="http://www.jooq.org/xsd/jooq-codegen-3.11.0.xsd">
<!-- Configure the database connection here -->
<jdbc>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://localhost:3306/library</url>
<user>root</user>
<password></password>
</jdbc>
<generator>
<!-- The default code generator. You can override this one, to generate your own code style.
Supported generators:
- org.jooq.codegen.JavaGenerator
- org.jooq.codegen.ScalaGenerator
Defaults to org.jooq.codegen.JavaGenerator -->
<name>org.jooq.codegen.JavaGenerator</name>
<database>
<!-- The database type. The format here is:
org.jooq.meta.[database].[database]Database -->
<name>org.jooq.meta.mysql.MySQLDatabase</name>
<!-- The database schema (or in the absence of schema support, in your RDBMS this
can be the owner, user, database name) to be generated -->
<inputSchema>library</inputSchema>
<!-- All elements that are generated from your schema
(A Java regular expression. Use the pipe to separate several expressions)
Watch out for case-sensitivity. Depending on your database, this might be important! -->
<includes>.*</includes>
<!-- All elements that are excluded from your schema
(A Java regular expression. Use the pipe to separate several expressions).
Excludes match before includes, i.e. excludes have a higher priority -->
<excludes></excludes>
</database>
<target>
<!-- The destination package of your generated classes (within the destination directory) -->
<packageName>test.generated</packageName>
<!-- The destination directory of your generated classes. Using Maven directory layout here -->
<directory>C:/workspace/MySQLTest/src/main/java</directory>
</target>
</generator>
</configuration>
이 자료는 MySQL과 윈도우 OS 기준으로 작성되어있으니 사용하는 DBMS와 OS에 맞춰 적절히 변경해주면 된다. 난 inputSchema 태그는 아예 지워버렸고, target 태그의 packageName과 directory 만 변경해주고 돌렸다.(물론 난 sqlite이니 MySQL부분은 sqlite에 맞게 변경)
작성이 완료됐으면 java command를 실행시켜준다.
java -classpath jooq-3.11.11.jar;jooq-meta-3.11.11.jar;jooq-codegen-3.11.11.jar;mysql-connector-java-5.1.18-bin.jar;.
org.jooq.codegen.GenerationTool library.xml
library.xml은 설정 xml 파일명이다. 물론 파일명은 꼭 library.xml 일 필요는 없다. 혹시 jooq 버전이 3.10 대라면 클래스명이나 패키지명이 좀 달라진부분이 있으니 주의하자. 어차피 내 코드들도 공식홈페이지에서 가져온거니 공식홈페이지에서 3.10 버전대를 참고하면 된다.
xml 설정에서 target 태그에 넣은 디렉토리의 패키지로 java 클래스 파일들이 만들어질것이다. 그럼 이를 import하면 위에 작성한 코드가 정상적으로 돌게된다.
gradle plugin
위 코드 생성할때 jar를 어떤 방식으로 가져왔는지는 잘 모르겠다. maven 으로 받아서 local 저장소에 가서 돌려도되고 아니면 maven이랑 별개로 따로 jar를 직접 다운받아서 돌렸을수도있다. 참고로 난 후자 방식으로했다. 뭐 잘 되니까 다행이긴한데 이 방식은 딱 봐도 불편하고 손이 많이간다. 거기에 라이브러리들 버전이 변경되면 이에따라 변경해야하는것들도 참 많다.
간단히 생각해봐도 빌드툴에서 지원하면 참 좋을것같다. 물론 이미 지원하고있으니 이를 사용하자. 내가 사용하는 빌드툴은 gradle이라서 gradle 기준으로 작성한다.메이븐도 당연히 지원하고, 앤트까지 지원하고있으니 이는 공식홈페이지에서 확인해보자.
gradle은 jooq에서 지원하는 스크립트를 이용해도되는데 3rd 파티로 개발된 gradle plugin이 존재한다. 3rd 파티라는게 조금 맘에 걸렸는데 이미 공식홈페이지에서부터 이를 추천하고있기때문에 비교적 부담없이 이용하기로했다.
문서도 잘 되어있는데 예제 코드를 봐보자. 먼저 gradle plugin을 선언한다.
plugins {
id 'nu.studer.jooq' version '3.0.3'
}
jooq 버전이 3.11 대라면 plugin도 3 버전대를 이용해야한다. jooq가 3.10 대라면 plugin은 2.0.11 을 이용하면된다. 아마도 아까 위에서 설명한 버전에 따라 달라지는점들에 대응하기위함인듯하다.
dependencies {
jooqRuntime 'postgresql:postgresql:9.1-901.jdbc4'
}
다음 의존성을 추가해준다. 물론 postgresql이 아니라면 다른걸로 바꿔줘야한다.
jooq {
service(sourceSets.main) {
jdbc {
driver = 'org.sqlite.JDBC'
url = 'jdbc:sqlite:src/main/resources/qsvc.splite3'
}
generator {
database {
name = 'org.jooq.meta.sqlite.SQLiteDatabase'
}
target {
packageName = 'com.jooq'
directory = "src/main/kotlin"
}
}
}
}
마지막으로 이런형태의 스크립트를 작성해준다. 수동으로 돌릴때 xml로 했던 설정을 gradle 스크립트로 작성하는 형태라고 보면된다. 위 스크립트에서 service 라고 적은 부분은 변수명정도로 보면 되니 service가 아니라 자신의 프로젝트에 맞는 명칭으로 변경해주면된다. 지금 이 설정은 클래스를 생성하기위한 최소형태의 스크립트이고 훨씬 많은 설정을 할 수 있다.
이 스크립트를 추가하고나면 gradle task로 generateServiceJooqSchemaSource 가 추가된다. Service는 위에 말한 변수명이니 service가 아닌 다른 이름을 지정하면 그에 맞게 변환된다. 그리고 gradle task를 실행하게되면 클래스가 생성되는걸 볼 수있다.
./gradlew clean generateServiceJooqSchemaSource
마무리
코드 생성외에 SQL을 실행시키는 구문은 현재 코드는 jooq를 이용해서 sql 구문을 생성하고, 그 실행은 jdbcTemplate이 하는 형태다. jooq를 더욱 적극적으로 쓴다면 jooq를 이용해서 직접 db를 호출할 수 도있다. 다음 포스팅이 존재한다면 그 내용으로 작성하지않을까 싶다.(더불어 클래스 생성 설정에 대한 내용도 좀 더 정리해야겠다.)
참고자료
'Java' 카테고리의 다른 글
parallelStream 남용으로인한 장애경험기 (2) | 2019.08.17 |
---|---|
spring boot에 jOOQ 적용하기#2 (0) | 2019.05.14 |
애노테이션 실습시 error: Could not instantiate an instance of processor 'com.yong.java10.AnnotationProcessor' 1 error 에러 (0) | 2018.09.15 |
dynamic proxy 구현하기 (0) | 2018.09.01 |
java9#01. 추가된 API 들 (0) | 2018.08.15 |
- Total
- Today
- Yesterday
- code
- Design Pattern
- mariadb
- db
- Jackson
- JavaScript Core
- toby
- go-core
- frontcode
- programming
- backend개발환경
- Kotlin
- http
- 정규표현식
- OOP
- frontend개발환경
- JPA
- servlet
- MySQL
- DesignPattern
- TEST
- spring cloud
- Spring
- EffectiveJava
- java8
- javascript
- Git
- generics
- clean code
- java
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |