🍣

Komapperで遊んでみる

2023/09/02に公開

※2023-09-02現在での情報ですので、最新情報は、 Komapper公式ドキュメント などを見てください

環境

komapper = 1.12.1
java = 20.0.2-tem
gradle = 8.3
kotlin = 1.9.10
db = h2, mysql:5.7

MySQL5.7での動作しないところ

※MySQL8ではちゃんと動作します!

https://github.com/momosetkn/komapper/blob/edf420c006fb63a3eb6efe1e7a74dbcc3474e5dd/integration-test-jdbc/src/test/kotlin/integration/jdbc/JdbcDeleteWhereTest.kt#L51-L54

こちらのコード、SQLが以下のように実行されます。

delete from employee as t0_

が、MySQL5.7では、以下エラーとなります。

[42000][1064] You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'as t0_' at line 1

原因

https://github.com/komapper/komapper/blob/20a2bdc967d054af8c78638d1544e2f59712c211/komapper-core/src/main/kotlin/org/komapper/core/dsl/builder/RelationDeleteStatementBuilder.kt#L26-L30
ここでdialect.supportsAliasForDeleteStatement()というフラグがtrueになっていると、エイリアス付きのDELETE文を生成するようです。

解決法

以下のようにフラグをfalseへoverrideする。これで、delete from employeeといったクエリになります

import org.komapper.jdbc.JdbcDatabase
import org.komapper.jdbc.JdbcDialect
import org.komapper.jdbc.JdbcDialects
import org.komapper.jdbc.DefaultJdbcDatabaseConfig

object MySQL5Dialect : JdbcDialect by JdbcDialects.get("mysql") {
    override fun supportsAliasForDeleteStatement() = false
}

val db by lazy {
    val config = DefaultJdbcDatabaseConfig(
        dataSource = hikariDatasource,
        dialect = MySQL5Dialect,
    )
    JdbcDatabase(config)
}

複数のdialectを使う場合の注意点

https://github.com/komapper/komapper/blob/00d719b18bf8891317cb78c4af385b7ab584c773/DESIGN_DOC.md#loosely-coupled-architecture
にあるとおり、ServiceLoaderを使っています。

で、以下のように複数のdialectを使う場合、FatJarにした際に問題点が出てきます。

    val komapperVersion = "1.12.1"
    implementation("org.komapper:komapper-dialect-h2-jdbc:$komapperVersion")
    implementation("org.komapper:komapper-dialect-mysql-jdbc:$komapperVersion")

エラーの内容

 Caused by: java.lang.IllegalStateException: The dialect is not found for the JDBC url. Try to add the 'komapper-dialect-mysql-jdbc' dependency. driver='mysql'
     at org.komapper.jdbc.JdbcDialects.get(JdbcDialects.kt:24)

原因

https://github.com/komapper/komapper/blob/bc289886313a85146b1c371d1869710e1115b634/komapper-jdbc/src/main/kotlin/org/komapper/jdbc/JdbcDialects.kt#L19-L25
ここで、ServiceLoader#loadを使っているため、FatJarにした際に、同じインターフェースを実装するクラスがServiceLoader.load経由で複数あると、うまくロードされないようです。

解決策

参考 Gradle Shadow Pluginで作成したfat/uber JARで、複数のJDBCドライバがロードできない

build.gradle.ktsに以下追加


plugins {
    // ~
    id("com.github.johnrengelman.shadow") version "8.1.1"
}
tasks {
    withType<com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar>() {
        mergeServiceFiles()
    }
}

Komapperで遊んでみた感想

SQLと乖離があまり無いDSL(SQLオジサンにとっては学習コストが低い)

例えば、こういうのとか…

Exposed

Member.select(Member.id eq id).single()

Komapper

val m = Meta.Member
QueryDsl.from(m).where { m.id eq id }.single()

Exposedのselectメソッドがwhereなのは…

eager loading的な機能も使いやすい

SQLAlchemy のjoined loadingと似た感じで使える。
joinしてincludeAll()メソッドを呼べば紐付いた状態で返ってくる

ドキュメントが充実

開発者がSeasar2やDomaといったJavaのフレームワークを作っていた日本人でもある

SQLを書きたい場合も対応している

Domaの上位互換という位置づけ?なので、SQLを書きたい場合も対応している
TEMPLATEクエリ | Komapper

Ktorm | Dialects and Native SQL
一方、Ktormは、SQLを書きたい場合は、jdbcのjava.sql.Connectionを直接さわれって…もはや、SQLを書きたかったら、他のDBアクセスライブラリ(例えば、Apache Commons DbUtilsとか)と組み合わせたほうが良さそう。

エンティティの自動生成機能がついている

開発初期にすでにデータベースがあって、それを使いたい場合とか、自動生成機能があると便利

Gradleプラグイン | Komapper
開発中に、自動生成結果とdiffを取って、逐一反映していくというのも良さそう。
※@KomapperOneToManyをつけたりして、結局、自動生成結果と一致しなくなるので、自動生成結果をそのまま使うのは難しいかも

Kotlinに最適化されているので、DSLが簡潔

Kotlinのinfix記法が使えるので、Java系ライブラリのようにカッコが多くならなくて良い

中間処理と終端処理などが分かりにくい問題が発生しない

// Komapperは、こうはなっていない
QueryDsl.from(m).where { m.id eq id }.toList()
    .map{
        MemberDto(
            id = it.id,
            name = it.name,
            address = it.address,
        )
    }

Komapperは、こうはなっていない。これだと、終端処理のメソッドを呼んだかどうかの意識をしないといけない。

// Komapperは、こうはなってる
db.runQuery(QueryDsl.from(m).where { m.id eq id }) 
    .map{
        MemberDto(
            id = it.id,
            name = it.name,
            address = it.address,
        )
    }

Komapperは、こうなっていて、メソッドチェーンでtoList()するよりも終端処理がされていることが分かりやすい
たしか、ScalaのORMのScalikeJDBCもそうなってたな…。

全体的に

いろいろ丁度いい(雑w)
しかし、使用者が少ないので、問題が起きたら、自分でエラーを解決するか、開発者本人に聞く必要がある。なので、もっとみんな使って欲しい…。

GitHubで編集を提案

Discussion