概要

サーバーサイドKotlinのためのORMライブラリ

Komapperとは?

KomapperはサーバーサイドKotlinのためのORMライブラリーです。 Kotlinの 1.5.31 以上をサポートします。

Komapperにはいくつかの強みがあります。

  • JDBCとR2DBCのサポート
  • コンパイル時のコード生成
  • 不変で合成可能なクエリ
  • Value Classのサポート
  • Spring Bootのサポート

JDBCとR2DBCのサポート

Komapperは JDBC もしくは R2DBC を用いてデータベースにアクセスできます。

KomapperはKotlinコルーチンの機能を活用することでJDBCとR2DBCの違いの大部分を吸収しています。

例えば、JDBCを用いるコードは次のように書けます。

fun main() {
    // create a Database instance
    val db = JdbcDatabase("jdbc:h2:mem:example;DB_CLOSE_DELAY=-1")

    // get a metamodel
    val a = Meta.address

    // execute simple CRUD operations in a transaction
    db.withTransaction {
        // create a schema
        db.runQuery {
            QueryDsl.create(a)
        }

        // INSERT
        val newAddress = db.runQuery {
            QueryDsl.insert(a).single(Address(street = "street A"))
        }

        // SELECT
        val address1 = db.runQuery {
            QueryDsl.from(a).where { a.id eq newAddress.id }.first()
        }
    }
}

一方でR2DBCを使うコードは次のように書けます。(上述のJDBC版との違いがわかるでしょうか?)

suspend fun main() {
    // create a Database instance
    val db = R2dbcDatabase("r2dbc:h2:mem:///example;DB_CLOSE_DELAY=-1")

    // get a metamodel
    val a = Meta.address

    // execute simple CRUD operations in a transaction
    db.withTransaction {
        // create a schema
        db.runQuery {
            QueryDsl.create(a)
        }

        // INSERT
        val newAddress = db.runQuery {
            QueryDsl.insert(a).single(Address(street = "street A"))
        }

        // SELECT
        val address1 = db.runQuery {
            QueryDsl.from(a).where { a.id eq newAddress.id }.first()
        }
    }
}

見た目上の違いは以下の2点のみです。

  1. R2DBC版ではmain関数がsuspend関数である
  2. dbインスタンスの生成方法が異なる

動作する完全なコードについては komapper-examples リポジトリ直下のconsole-jdbcとconsole-r2dbcのプロジェクトを参照ください。

コンパイル時のコード生成

Komapperは Kotlin Symbol Processing API を使ってコンパイル時にデータベースアクセスに必要なメタモデル(テーブルやカラムの情報)をKotlinのソースコードとして生成します。

この仕組みによりKomapperは実行時にリフレクションを用いたりデータベースからメタデータを読み取ったりする必要がありません。 そのため実行時の信頼性とパフォーマンスが向上します。

コード生成はアノテーションの読み取りによって行われます。 例えば、AddressクラスをADDRESSテーブルにマッピングさせる場合次のように記述できます。

data class Address(
    val id: Int,
    val street: String,
    val version: Int
)

@KomapperEntityDef(Address::class)
data class AddressDef(
    @KomapperId val id: Nothing,
    @KomapperVersion val version: Nothing,
)

生成されたメタモデルはorg.komapper.core.dsl.Metaオブジェクトの拡張プロパティを介してアプリケーションに公開されます。 アプリーケーションはメタモデルを使うことでタイプセーフにクエリを組み立てられます。

// get a generated metamodel
val a = Meta.address

// define a query
val query = QueryDsl.from(a).where { a.street eq "STREET 101" }.orderBy(a.id)

アノテーションを使ったマッピングに関する詳細は エンティティクラス を、 コンパイル時のアノテーション処理に関する詳細は アノテーションプロセッシング を参照ください。

不変で合成可能なクエリ

Komapperのクエリは実質的に不変(immutable)です。 従って、状態の共有に伴う不具合を心配することなく安全に合成可能(composable)です。

// get a generated metamodel
val a = Meta.address

// define queries
val query1 = QueryDsl.from(a)
val query2 = query1.where { a.id eq 1 }
val query3 = query2.where { or { a.id eq 2 } }.orderBy(a.street)
val query4 = query1.zip(query2)
    
// issue "select * from address"
val list1 = db.runQuery { query1 }
// issue "select * from address where id = 1"
val list2 = db.runQuery { query2 }
// issue "select * from address where id = 1 or id = 2 order by street"
val list3 = db.runQuery { query3 }
// issue "select * from address" and "select * from address where id = 1"
val (list4, list5) = db.runQuery { query4 }

where関数などを使って既存のクエリを基に他のクエリを作るだけでなく、 zip関数などを使って複数のクエリを単一のクエリに合成することもできます。

詳細は クエリの合成 を参照ください。

Value Classのサポート

Value Classをエンティティクラスのプロパティとして利用できます。 利用に当たって特別な設定は不要です。

@JvmInline
value class Age(val value: Int)

data class Employee(val id: Int = 0, val name: String, val age: Age)

@KomapperEntityDef(Employee::class)
data class EmployeeDef(@KomapperId @KomapperAutoIncrement val id: Nothing)

クエリでの利用例です。

val e = Meta.employee
val query = QueryDsl.from(e).where { e.age greaterEq Age(40) }
val seniorEmployeeList = db.runQuery { query }

Spring Bootのサポート

KomapperはSpring Bootとの組み合わせを容易にするstarterを提供します。

例えば、JDBCを使ったデータアクセスをSpring Bootと組み合わせて行いたい場合、 Gradleのdependenciesブロックに次のような設定をするだけで必要なライブラリの依存性が解決されSpring Boot管理のデータソースやトランザクションと連動します。

val komapperVersion: String by project

dependencies {
    implementation("org.komapper:komapper-spring-boot-starter-jdbc:$komapperVersion")
    implementation("org.komapper:komapper-dialect-h2-jdbc:$komapperVersion")
}

動作する完全なコードについては komapper-examples リポジトリ直下のspring-boot-jdbcとspring-boot-r2dbcのプロジェクトを参照ください。

関連情報として スターター も参照ください。

次に見るべきドキュメント

最終更新 March 11, 2022: Use the `suspend` keyword (ee4912e)