以下の内容はhttps://uga-box.hatenablog.com/entry/2024/07/23/000000より取得しました。


【Kotlin】KotlinのORMライブラリのExposedについて

KotlinのORMライブラリのExposedを知ったので調べた

jetbrains.github.io

Exposedは、JetBrains社が開発したKotlin専用の軽量SQLライブラリ

特徴の1つとして、テーブルアクセスのやり方が2通りある

  • DSL (Domain Specific Language)
    • 複雑なクエリや結合が必要な場合
    • データベースレイヤーとアプリケーションレイヤーを明確に分離したい場合
    • SQLの柔軟性が必要な場合
  • DAO (Data Access Object)
    • シンプルなCRUD操作が中心の場合
    • オブジェクト指向的なアプローチを好む場合
    • アプリケーションレイヤーでデータベース操作を直接扱いたい場合

DSL

Users.select { Users.name eq "John" }
     .limit(10)
     .orderBy(Users.id to SortOrder.ASC)
     .firstOrNull()

DAO

User.find { Users.name eq "John" }
    .sortedBy { it.id }
    .firstOrNull()

以前、Java開発していた時に、複雑な検索をやりたくてSQLを書いた方が楽な場合と、更新系のようにDAOの機能に委ねたい場合があったので、クエリ系はMyBatisで、コマンド系はDomaを使っていたが、同じライブラリで使い分けができるのはいいかもしれない

トランザクション管理

Exposedでは、すべてのデータベース操作はtransactionブロック内で実行する必要がある

transaction {
    // データベース操作をここに記述
}

このブロックは自動的にトランザクションを開始し、ブロックの終了時にコミットする

例外が発生した場合、トランザクションは自動的にロールバックされる

必要に応じて、トランザクション内で手動でコミットやロールバックを行うこともできる

transaction {
    // 一部の操作
    commit()
    // 他の操作
    rollback()
}

トランザクションの分離レベルは、以下のように指定できる

transaction(
    Dispatchers.IO + 
    transactionIsolation(Connection.TRANSACTION_SERIALIZABLE)
) {
    // トランザクション処理
}

あとは、Nested transaction できるらしいが詳しくは調べられなかった

参考
Exposed 応用編 〜内部実装 と 魔拡張〜 - Speaker Deck

テーブル定義

Exposedでテーブルを定義するには、Tableクラスを継承したオブジェクトを作成する

object Users : Table() {
    val id = integer("id").autoIncrement()
    val name = varchar("name", 50)
    val email = varchar("email", 100)
    
    override val primaryKey = PrimaryKey(id)
}

主キーは、primaryKeyプロパティをオーバーライドして設定

複合主キーの場合は PrimaryKey(column1, column2) のように設定

外部キーは以下のように設定

val userId = integer("user_id").references(Users.id)

インデックスの作成は以下のように設定

index(isUnique = true, name = "email_unique_idx", email)

デフォルト値の設定

val createdAt = datetime("created_at").default(CurrentDateTime)

NULL許容の設定

val description = varchar("description", 255).nullable()

テーブル名のカスタマイズ

object Users : Table("tbl_users") {
    // カラム定義
}

マイグレーション

SchemaUtilsを使用してテーブルの作成や変更が可能

SchemaUtils.create(Users, Orders)  // テーブルの作成
SchemaUtils.drop(Users, Orders)    // テーブルの削除

Flywayと併用可能らしいので、これは別途で調べる

テストではこんな感じでスキーマをいじれるみたい

class MyTest {
    @Before
    fun setup() {
        Database.connect("jdbc:h2:mem:test", driver = "org.h2.Driver")
        transaction {
            SchemaUtils.create(Users, Orders)
        }
    }
    
    @After
    fun tearDown() {
        transaction {
            SchemaUtils.drop(Users, Orders)
        }
    }
    
    // テストメソッド...
}

コネクション管理

exposedのDB接続は以下のような感じ

Database.connect(
    url = "jdbc:mysql://localhost:3306/mydatabase",
    driver = "com.mysql.cj.jdbc.Driver",
    user = "username",
    password = "password"
)

デフォルトでは、Exposedは内部的にHikariCPを使用してコネクションプールを管理する

カスタマイズが必要な場合は、以下のように設定できる

val config = HikariConfig().apply {
    jdbcUrl = "jdbc:mysql://localhost:3306/mydatabase"
    driverClassName = "com.mysql.cj.jdbc.Driver"
    username = "username"
    password = "password"
    maximumPoolSize = 10
}
val dataSource = HikariDataSource(config)
Database.connect(dataSource)

複数のデータベースを使用する場合、それぞれに名前を付けて管理できる

val db1 = Database.connect("jdbc:mysql://localhost:3306/db1", driver = "com.mysql.cj.jdbc.Driver")
val db2 = Database.connect("jdbc:postgresql://localhost:5432/db2", driver = "org.postgresql.Driver")

transaction(db1) {
    // db1に対する操作
}
transaction(db2) {
    // db2に対する操作
}

必要に応じて、コネクションを明示的に取得することもできる

transaction {
    val connection = TransactionManager.current().connection
    // connectionを使用した処理
}

読み取り専用のトランザクション

transaction(readOnly = true) {
    // 読み取り専用の操作
}



以上の内容はhttps://uga-box.hatenablog.com/entry/2024/07/23/000000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14