しおしお

IntelliJ IDEAのことなんかを書いてます

KtorでDoma2を使ってデータベースアクセスしてみた

Ktor - asynchronous Web framework for KotlinでDoma2を使ってデータベースアクセスしてみました。 Ktorの公式サイト上にはデータベースアクセスする方法などが全く無いので、これが正解かどうかはわかりませんが…

build.gradle

  • データベースアクセスに必要となるライブラリを追加します
dependencies {
  kapt 'org.seasar.doma:doma:2.22.0'
  implementation 'org.seasar.doma:doma:2.22.0'
  runtime 'org.postgresql:postgresql:42.2.5'
  implementation 'com.zaxxer:HikariCP:3.3.1'
}
  • Doma2でkaptが必要になるので、kaptプラグインを追加します
apply plugin: 'kotlin-kapt'

データベースの接続先を定義する

  • resources/application.confにデータベースの接続先などを定義します
database {
    driverClass = org.postgresql.Driver
    url = "jdbc:postgresql://localhost:5432/siosio"
    user = siosio
    password = siosio
}

DomaのConfigクラスを追加する

  • トランザクション — Doma 2.0 ドキュメントを参考にシングルトンなコンフィグを作ります
    Kotlinなので、objectで作ってsingleton@JvmStaticにしています
  • initで、HikariCPのDataSourceを作成します
    接続先などの情報は、resources/application.conf からとってきます
@SingletonConfig
object DomaConfig : Config {
    
    private lateinit var ds: LocalTransactionDataSource
    
    fun init(environment: ApplicationEnvironment) {
        val hikariConfig = HikariConfig()
        hikariConfig.driverClassName = environment.config.property("database.driverClass").getString()
        hikariConfig.jdbcUrl = environment.config.property("database.url").getString()
        hikariConfig.username = environment.config.property("database.user").getString()
        hikariConfig.password = environment.config.property("database.password").getString()
        ds = LocalTransactionDataSource(HikariDataSource(hikariConfig))
    }

    override fun getDialect(): Dialect = PostgresDialect()

    override fun getDataSource(): DataSource {
        if (::ds.isInitialized.not()) {
            throw IllegalStateException("database setting is not initialized")
        }
        return ds
    }

    override fun getTransactionManager(): TransactionManager {
        return LocalTransactionManager(ds.getLocalTransaction(jdbcLogger))
    }

    @JvmStatic
    fun singleton(): DomaConfig = DomaConfig
}

Moduleの定義タイミングでDataSourceを作成する

  • DomaConfig#initを呼び出してDataSourceを作成します
fun Application.module(testing: Boolean = false) {
    // 省略

    DomaConfig.init(environment)
    
    routing {
        user()
    }
}

Domaを使いやすくするヘルパーメソッドを追加する

  • Daoの実装クラス(DaoImpl)を取得するメソッドを追加します
  • transactionを実行するためのメソッドを追加します
inline fun <reified T> dao(): T {
    return Thread.currentThread().contextClassLoader
            .loadClass("${T::class.qualifiedName}Impl")
            .newInstance() as T
}

fun <T> PipelineContext<out Any, out Any>.transaction(block: () -> T): T {
    return DomaConfig.transactionManager.required(block)
}

データベースアクセス処理を実装する

  • 先程定義したtransactionメソッドに渡したブロック内でデータベースアクセスを行います
  • 確認用に登録処理と、登録したデータを一括取得する処理を実装しています
fun Route.user(): Unit {
    get("/users") {
        val userList = transaction {
            dao<UserDao>().findAll()
        }
        call.respond(userList)
    }

    post("/users") {
        val user = call.receive(UserEntity::class)
        transaction {
            dao<UserDao>().insert(user)
        }
        call.respond(HttpStatusCode.Created)
    }
}

アプリケーションを実行して確認してみる

データの登録処理

登録処理を呼び出して、2件データを登録してみます

~ ❮❮❮ curl -H 'Content-Type: application/json' -d '{"name": "hoge"}' -D - http://localhost:8080/users
HTTP/1.1 201 Created
Content-Length: 0

~ ❯❯❯ curl -H 'Content-Type: application/json' -d '{"name": "fuga"}' -D - http://localhost:8080/users
HTTP/1.1 201 Created
Content-Length: 0

データの取得処理

一括取得を呼び出すと登録したデータが返されることが確認できます

~ ❯❯❯ curl  -D - http://localhost:8080/users
HTTP/1.1 200 OK
Content-Length: 72
Content-Type: application/json; charset=UTF-8

[ {
  "id" : 2,
  "name" : "hoge"
}, {
  "id" : 3,
  "name" : "fuga"
} ]

データベースの確認

登録した2件のデータが確認できます。ちゃんと動いていますね。

siosio=# select * from users;
 id | name 
----+------
  2 | hoge
  3 | fuga
(2 rows)