iT邦幫忙

2025 iThome 鐵人賽

DAY 6
0

在串接資料庫的部分,Exposed 的用法已經在之前的 [Day 18] 資料表關聯,DAO 的一對多關係[Day 19] 談談 n+1 問題和 eager loading[Day 20] exposed 的多對多關聯 提過了,用法在這次改變上沒什麼差異。

不過這次 Exposed 還多了一個改變,很值得特別說一下。那就是除了最早支援的 JDBC,現在 Exposed 還支援 R2DBC 實作了。

Exposed 最早是設計在 JDBC 上,JDBC 的特色是同步、阻塞式,也就是查詢資料庫的時候會卡住一個線程。

但在 Ktor 這個原生支援非阻塞 I/O 的框架內,和其他非阻塞的程式放在一起時,一但效能需求提高時,這時候 JDBC 就會變成瓶頸。

這時候引用 R2DBC (Reactive Relational Database Connectivity),由於 R2DBC 本身的設計是非阻塞的,不會佔用一整個線程,所以特別適合高併發或 I/O 密集的場景。

我們來改寫看看之前的程式。首先我們加上對應的 gradle dependencies

我們先到 gradle/libs.versions.toml 調整 exposed 版本號碼

[versions]
exposed = "1.0.0-beta-5"
implementation("org.jetbrains.exposed:exposed-r2dbc:1.0.0-rc-1")
implementation("com.h2database:h2:2.2.224")
implementation("io.r2dbc:r2dbc-h2:1.0.0.RELEASE") // H2 R2DBC driver

接著我們將這段

val database = Database.connect(
    url = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1",
    user = "root",
    driver = "org.h2.Driver",
    password = "",
)

改寫成

val database = R2dbcDatabase.connect {
    defaultMaxAttempts = 1
    defaultR2dbcIsolationLevel = IsolationLevel.READ_COMMITTED

    setUrl("r2dbc:h2:mem:///test;DB_CLOSE_DELAY=-1;")
}

UserService 內所有的引用也要跟著改變

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.Serializable
import org.jetbrains.exposed.v1.dao.IntEntity
import org.jetbrains.exposed.v1.dao.IntEntityClass
import org.jetbrains.exposed.v1.core.dao.id.EntityID
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
import org.jetbrains.exposed.v1.r2dbc.R2dbcDatabase
import org.jetbrains.exposed.v1.r2dbc.transactions.*
import org.jetbrains.exposed.v1.r2dbc.SchemaUtils

接著就是將 UserService 裡面阻塞的 transaction 都改成 suspendTransaction了。

這邊要注意 init 的部分是阻塞的,在將裡面的 transaction 改成不阻塞的 suspendTransaction 時會出錯,由於這邊不是效能的主要瓶頸,這邊先簡單地用 runBlocking 包起來

init {
    runBlocking {
        suspendTransaction {
            SchemaUtils.create(Users)
        }
    }
}

dbQuery 改成統一使用不會阻塞的 suspendTransaction

import org.jetbrains.exposed.v1.r2dbc.transactions.*

private suspend fun <T> dbQuery(block: suspend () -> T): T =
    suspendTransaction(Dispatchers.IO) { block() }

今天的部分就到這邊,我們明天見!


上一篇
Day 05:用 DAO 改寫 UserService 並加上自動化測試
下一篇
Day 07:Ktor 的依賴注入
系列文
每天一點 Ktor 3.0:一個月學會 Kotlin 後端開發7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言