Kotlin + Gradle + JDBC でCRUD処理を実行する
概要
Kotlin の勉強をしたかったのですが、Kotlin と JDBC(SpringJDBC)を用いて、CRUD をする記事があまり見つかりませんでした。
「Kotlin で簡単な CRUD 操作を知りたい」「JDBC みたいな実装が薄い DB クライアントを使いたい」「MyBatis は実装が複雑」といった目的のために作成しました。
素の JDBC ではなく、SpringJDBC を用いているのは、Web フレームワークである Spring は今後使う可能性が高いので、Spring 前提で記事を作成しました。
完成したソースコードは以下になります。
前提
プログラミング言語と DB は以下の構成で実装します。
Kotlin と PostgrSQL の基本的なこと、Intellij の使い方については知っている前提です。
項目 | 要素 |
---|---|
プログラミング言語 | Kotlin |
DB | PostgreSQL |
OS | macOS |
IDE | Intellij IDEA 2022.1.1(Apple Silicon 版) |
本記事では、Spring Initializr を用いて初期環境を作成しますが、Dependencies に SpringBoot Web(Spring MVC)を使用しません。
JDBC の使い方と、SpringBoot の使い方を切り分けるためです。
環境構築
Spring Initializr
Spring Initializr を用いて、ひな型を作成します。
以下のリンクから、Spring Initializr の画面を開いてください。
設定は以下にします。
項目 | 要素 |
---|---|
Project | Gradle Project |
Language | Kotlin |
Spring Boot | 2.7.0 |
Project Metadata Artifact | kotlin-gradle-jdbc-demo |
Project Metadata Name | kotlin-gradle-jdbc-demo |
Packaging | Jar |
Java | 17 |
Dependencies | PostgreSQL Driver、JDBC API |
Spring Initializr の設定画面
「Generate」を押下し、zip ファイル(kotlin-gradle-jdbc-demo.zip)がダウンロードします。
適当なディレクトリに移動します。
そして、Intellij でプロジェクトを開いてください。初回は依存ツールのインストールのため時間がかかります。
何もしていなければ以下のようなディレクトリ構成になっています。
> tree -L 2
.
├── HELP.md
├── build.gradle.kts
├── gradle
│ └── wrapper
├── gradlew
├── gradlew.bat
├── settings.gradle.kts
└── src
├── main
└── test
5 directories, 5 files
DB
Kotlin と JDBC の環境構築は終わりましたが、JDBC はアプリケーション起動時に、DB との接続が確立していなければ、アプリケーションが落ちてしまいます。
docker-compose を用いて、DB の環境構築をします。
プロジェクトのルートディレクトリにdocker-compose.yaml
を作成して、以下の記述をしてください。
---
version: '3.8'
services:
#
# PostgreSQL
#
sample-pg:
image: postgres:14-bullseye
container_name: sample-pg
ports:
- 5432:5432
environment:
POSTGRES_USER: sample-user
POSTGRES_PASSWORD: sample-pass
POSTGRES_DB: sample-db
POSTGRES_INIT_DB_ARGS: --encoding=UTF-8
volumes:
- type: bind
source: ${PWD}/sql/
target: /docker-entrypoint-initdb.d/
続いて、DB に初期データを挿入するための、SQL を作成します。
./sql
ディレクトリを作成し、001-customer.sql
に以下の SQL を記述してください。
DROP TABLE IF EXISTS customer;
CREATE TABLE IF NOT EXISTS customer (
id SERIAL,
first_name VARCHAR(255),
last_name VARCHAR(255)
);
INSERT INTO customer ( first_name, last_name ) VALUES
( 'Alice', 'Sample1' )
, ( 'Bob', 'Sample2' )
;
DB の起動を確認します。以下のコマンドで起動できなかったら、docker の設定を確認してください。
# DB を起動
docker compose up sample-pg
# DBを停止 & 削除
# docker compose down
最後に JDBC と DB の接続設定を追加します。
spring.datasource.url=jdbc:postgresql://127.0.0.1:5432/sample-db
spring.datasource.username=sample-user
spring.datasource.password=sample-pass
spring.datasource.driver-class-name=org.postgresql.Driver
以上で環境構築は完了です。
実装
これから、Kotlin を用いた CRUD 実装します。
簡略化のため、./src/main/kotlin/com/example/kotlingradlejdbcdemo
配下にすべてのファイルを配置します。
Create
まず、Create からできるようにします。
jdbc を用いた SQL の実行は簡潔で、副作用がある処理はupdate
で、副作用がない処理はquery
(queryForStream
)で実行可能です。
今回は、INSERT 処理(副作用がある処理)なのでjdbcTemaplate.update
を使用します。
(Intellij にはProperty 'jdbcTemplate' could be private
と怒られますが、今回の本質ではないので無視します。@Component
をつければ消えます)。
package com.example.kotlingradlejdbcdemo
import org.springframework.jdbc.core.JdbcTemplate
interface InsertCommand {
fun perform(firstName: String, lastName: String)
}
class InsertCommandImpl(val jdbcTemplate: JdbcTemplate) : InsertCommand {
val sql = "INSERT INTO customer(first_name, last_name) VALUES (?, ?);"
override fun perform(firstName: String, lastName: String) {
jdbcTemplate.update(sql, firstName, lastName)
}
}
Read
続いて、Read についてです。
最初に data class を用いて Customer クラスを実装します。
package com.example.kotlingradlejdbcdemo
data class Customer(
val id: Long,
val firstName: String,
val lastName: String,
)
続いて、Customer クラスと DB の O/R マッパとなる CustomRowMapper クラスを作成します。
jdbc に用意されている、RowMapper
インタフェースを用いることで、簡単に DB のカラムと対応づけが可能です。
import org.springframework.jdbc.core.RowMapper
import java.sql.ResultSet
class CustomerRowMapper : RowMapper<Customer> {
override fun mapRow(rs: ResultSet, rowNum: Int): Customer? {
return Customer(
rs.getLong("id"),
rs.getString("first_name"),
rs.getString("last_name"),
)
}
}
最後に、これらのクラスを組み合わせて、テーブル内のすべてのクラスを取得する処理を実装します。
基本的には Read を実装したときと同様です。
複数のカラムを取得したときには、jdbcTemplate.queryForStream()
を使用します。
jdbcTemplate.queryForStream()
が返す型はStream
のため、perform()
の戻り値をList<Customer>
にする必要があります。
そこで、toList()
を文末につけることで変換されます。
package com.example.kotlingradlejdbcdemo
import org.springframework.jdbc.core.JdbcTemplate
interface SelectAllQuery {
fun perform(): List<Customer>
}
class SelectAllQueryImpl(val jdbcTemplate: JdbcTemplate) : SelectAllQuery {
val sql = """
SELECT id, first_name, last_name
FROM customer;
""".trimIndent()
val mapper = CustomerRowMapper()
override fun perform(): List<Customer> {
return jdbcTemplate.queryForStream(sql, mapper).toList()
}
}
Update
続いて、Update についてです。
Update は、Create と同様の処理を実装すれば完了します。
package com.example.kotlingradlejdbcdemo
import org.springframework.jdbc.core.JdbcTemplate
interface UpdateCommand {
fun perform(lastName: String)
}
class UpdateCommandImpl(val jdbcTemplate: JdbcTemplate) : UpdateCommand {
val sql = """
UPDATE
customer
SET
first_name = 'Eve'
, last_name = 'Sample4'
WHERE
last_name = ?
;
""".trimIndent()
override fun perform(lastName: String) {
jdbcTemplate.update(sql, lastName)
}
}
Delete
最後に Delete です。
Delete も Create、Update 同様の実装をすれば完了です。
package com.example.kotlingradlejdbcdemo
import org.springframework.jdbc.core.JdbcTemplate
interface DeleteCommand {
fun perform(firstName: String)
}
class DeleteCommandImpl(val jdbcTemplate: JdbcTemplate) : DeleteCommand {
val sql = "DELETE FROM customer WHERE first_name = ?"
override fun perform(firstName: String) {
jdbcTemplate.update(sql, firstName)
}
}
動作確認
では、実際に動作確認できているか確認します。
./src/main/kotlin/com/example/kotlingradlejdbcdemo/KotlinGradleJdbcDemoApplication.kt
にクラスの実行を記述します。
以下のように記述してください。
jdbcTemplate
は DI してくれるので、実行時に引数をインスタンスにしてくれます。
package com.example.kotlingradlejdbcdemo
import org.springframework.boot.ApplicationArguments
import org.springframework.boot.ApplicationRunner
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.runApplication
import org.springframework.jdbc.core.JdbcTemplate
@SpringBootApplication
class KotlinGradleJdbcDemoApplication(val jdbcTemplate: JdbcTemplate) : ApplicationRunner {
override fun run(args: ApplicationArguments?) {
println("--Create--")
InsertCommandImpl(jdbcTemplate).perform("Carol", "Sample3")
println("--Read--")
SelectAllQueryImpl(jdbcTemplate).perform().forEach { println(it) }
println("--Update--")
UpdateCommandImpl(jdbcTemplate).perform("Sample3")
println("--Read--")
SelectAllQueryImpl(jdbcTemplate).perform().forEach { println(it) }
println("--Delete--")
DeleteCommandImpl(jdbcTemplate).perform("Eve")
println("--Read--")
SelectAllQueryImpl(jdbcTemplate).perform().forEach { println(it) }
}
}
fun main(args: Array<String>) {
runApplication<KotlinGradleJdbcDemoApplication>(*args)
}
では、動作確認します。
DB を起動してください。
# DB を起動
docker-compose up sample-pg pg-web
# DBを停止 & 削除
# docker-compose down
DB の起動を確認できたら、できたらアプリケーションを実行します。
Intellij の実行ボタンを押してもよいですが、ここでは ./gradlew
を用いて CLI で起動します。
以下を実行してください。
./gradlew bootRun
いろいろログがでてきます。
以下のように、Create、Read、Update、Delete、を確認できたら、成功です。
--Create--
2022-06-02 07:47:41.925 INFO 16013 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-06-02 07:47:42.098 INFO 16013 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
--Read--
Customer(id=1, firstName=Alice, lastName=Sample1)
Customer(id=2, firstName=Bob, lastName=Sample2)
Customer(id=3, firstName=Carol, lastName=Sample3)
--Update--
--Read--
Customer(id=1, firstName=Alice, lastName=Sample1)
Customer(id=2, firstName=Bob, lastName=Sample2)
Customer(id=3, firstName=Eve, lastName=Sample4)
--Delete--
--Read--
Customer(id=1, firstName=Alice, lastName=Sample1)
Customer(id=2, firstName=Bob, lastName=Sample2)
2022-06-02 07:47:42.279 INFO 16013 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-06-02 07:47:42.284 INFO 16013 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
まとめ
Kotlin、JDBC、PostgreSQL を用いた簡単な CRUD ができるアプリケーションを実装、紹介しました。
ここまでの内容を手を動かして写経するだけでも、Kotlin の型システムや、Intellij が持つ補完機能の強力さを感じられます。
ぜひ、Kotlin で簡単なことを実装してみたいけど悩んでいる方の助けになれば幸いです。
今回は実利用を完全に無視した内容ですので、次回は Web API の作成を考えています。
参考
Discussion