KotlinのORMライブラリのExposedを知ったので調べた
Exposedは、JetBrains社が開発したKotlin専用の軽量SQLライブラリ
特徴の1つとして、テーブルアクセスのやり方が2通りある
- DSL (Domain Specific Language)
- 複雑なクエリや結合が必要な場合
- データベースレイヤーとアプリケーションレイヤーを明確に分離したい場合
- SQLの柔軟性が必要な場合
- DAO (Data Access Object)
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) { // 読み取り専用の操作 }