算譜王におれはなる!!!!

偏りはあると思うけど情報技術全般についてマイペースに書くよ。

KotlinでDBアクセスしてみた(原始的な方法、標準ライブラリ、3rdパーティライブラリ)

練習用のテーブル

create table articles(
  id integer primary key,
  title varchar(20) not null,
  author varchar(20),
  content text not null
);

原始的な方法

Javaと同じ方法でゴリゴリ書く。

import java.sql.DriverManager
import java.sql.ResultSet
import java.sql.Statement
import java.sql.Connection
 
fun main(args: Array<String>) {
    val articles = findAllArticles()
    println(articles)
}
 
fun findAllArticles(): List<Article> {
    var conn: Connection? = null
    var statement: Statement? = null
    var resultSet: ResultSet? = null
 
    val articleListBuilder = ImmutableArrayListBuilder<Article>()
 
    try {
        conn = DriverManager.getConnection("jdbc:postgresql://localhost/sample")
        statement = conn?.createStatement()
        resultSet = statement?.executeQuery("select * from articles")
        while(resultSet?.next() ?: false) {
            articleListBuilder.add(Article(
                    resultSet!!.getLong(1),
                    resultSet!!.getString(2)!!,
                    resultSet!!.getString(3) ?: "",
                    resultSet!!.getString(4)!!
            ))
        }
    } finally {
        resultSet?.close()
        statement?.close()
        conn?.close()
    }
 
    return articleListBuilder.build()
}
 
data class Article(
        val id: Long,
        val title: String,
        val author: String,
        val content: String
)

Javaよりは簡単に書ける。チェック例外がないだけでだいぶスッキリするなぁ。 Connectionとかのclose()はちょっと面倒臭いけど仕方ない。Kotlin標準ライブラリにはCloseableの拡張関数useがあって、これを使えば自分でクローズ書かなくてもいいんだけど、ConnectionとかはCloseableじゃない。

Kotlin標準ライブラリを使う方法

標準ライブラリかどうかはわからないけど、プロジェクトKotlinの一部として開発されてるパッケージkotlin.jdbcを使う方法。 使うにはリポジトリからクローンして、クラスパスを通す必要がある。

import kotlin.jdbc.getConnection
import kotlin.jdbc.use
import kotlin.jdbc.useSql
import kotlin.jdbc.iterator
 
fun main(args: Array<String>) {
    val articles = findAllArticles()
    println(articles)
}
 
fun findAllArticles(): List<Article> {
    val articleListBuilder = ImmutableArrayListBuilder<Article>()
 
    getConnection("jdbc:postgresql://localhost/sample").use {
 
        // useSql -> use にリネーム予定
        it.createStatement()?.useSql {
            it.executeQuery("select * from articles").use {
                it.iterator().forEach {
                    articleListBuilder.add(Article(
                        it.getLong(1),
                        it.getString(2)!!,
                        it.getString(3) ?: "",
                        it.getString(4)!!
                    ))
                }
            }
        }
    }
 
    return articleListBuilder.build()
}
 
data class Article(
        val id: Long,
        val title: String,
        val author: String,
        val content: String
)

try-catchもクローズもvarも消えてイイ感じ。でもそれだけ。

ちなみにStatementの拡張関数useSqluseにリネーム予定(ちょっと残念なバグがあるらしい)。

サードパーティ製ライブラリを使う方法

KotlinでSQLを意識させないためのサードパーティ製ライブラリがあるので使ってみた。 Exposedっていうライブラリで、一応プロトタイプという位置付け。 軽量さを売りにしてるのかな。もうちょっとカッコよくできそうな感じはする。 あとプロトタイプってことなので、まだまだ機能が充実してないです。

使うためにはリポジトリから引っ張って来て、クラスパスとかを上手いこと設定する。

import kotlin.sql.Database
import kotlin.sql.Table
import kotlin.sql.integer
import kotlin.sql.varchar
import kotlin.sql.template

fun main(args: Array<String>) {
    val articles = findAllArticles()
    println(articles)
}

fun findAllArticles(): List<Article> {
    val articleListBuilder = ImmutableArrayListBuilder<Article>()

    Database("jdbc:postgresql://localhost/sample", "org.postgresql.Driver").withSession {
        Articles.all().forEach {
            val (id, title, author) = it
            articleListBuilder.add(Article(
                    id.toLong(),
                    title,
                    author,
                    ""
            ))
        }
    }

    return articleListBuilder.build()
}

object Articles: Table() {
    val id = integer("id").primaryKey()
    val title = varchar("title", length = 20)
    val author = varchar("author", length = 20)
    val content = varchar("content", length = 3000)

    val all = template(id, title, author) // 注
}

data class Article(
        val id: Long,
        val title: String,
        val author: String,
        val content: String
)

「注」の箇所。ここではselect id, title, author相当のSQLを構築する元を作ってるんだけど、このtemplate関数は現状では最大3つの引数しか取れないっぽい。そのため今回の例ではcontentを取るのを諦めた。その他にはカラムのデータ型が整数値と文字列の2種類しかないなど、作り始めて間もない感じが伝わってきた。

Articles.all().forEachのブロックの中で、it(ここではTripleオブジェクト)の各値を多重宣言を使って各名前にバインドしている。

感想

カッコよくて便利なライブラリを作ろうと思いました。