前言
对于 Android 老兵来说,操作数据库通常意味着 SQLite 和 Room。但在 Ktor 后端领域,我们有更强大的伙伴 —— Exposed。
Exposed 是 JetBrains 官方出品的轻量级 SQL 库。它既能给你 SQL 的控制感,又能让你享受 Kotlin 类型安全带来的快感。今天,我们就来实战搭建一套用户系统的持久化层。
一、 DSL 模式 vs DAO 模式:怎么选?
Exposed 提供了两种“口味”:
- DSL (Domain Specific Language):
- 特点:直接用 Kotlin 写 SQL,极其接近原生查询,性能最高。
- 场景:适合追求性能、或者需要写极其复杂 Join 查询的场景。
- DAO (Data Access Object):
- 特点:经典的 ORM 体验。定义一个类,操作它的属性。
- 场景:适合典型的 CRUD(增删改查)业务。
二、 实战:定义你的 Table
在 Exposed 中,表结构是单例对象,这保证了全局的字段引用一致性。
object Users : Table("users") {
val id = integer("id").autoIncrement()
val username = varchar("username", 50).uniqueIndex()
val password = varchar("password", 255)
val createdAt = datetime("created_at").defaultExpression(CurrentDateTime)
override val primaryKey = PrimaryKey(id)
}
三、 数据库连接池:生产环境必备
不要在每个请求里都去 Database.connect!
在生产环境下,我们需要使用连接池(如 HikariCP)。
fun initDB() {
val config = HikariConfig().apply {
driverClassName = "org.postgresql.Driver"
jdbcUrl = "jdbc:postgresql://localhost:5432/mydb"
username = "admin"
password = "password"
maximumPoolSize = 10 // 根据 CPU 核心数调整
}
Database.connect(HikariDataSource(config))
}
四、 事务管理:newSuspendedTransaction
这是最硬核的部分。后端操作数据库必须在事务中,而且由于 Ktor 是异步的,我们必须使用支持协程的事务:
suspend fun <T> dbQuery(block: suspend () -> T): T =
newSuspendedTransaction(Dispatchers.IO) {
block()
}
五、 架构建议
- Repository 层隔离:不要在 Route 块里直接写数据库查询。定义
UserRepository,将 Exposed 的逻辑封装在内,返回简单的 Data Class 给 UI/Route。 - Schema 自动迁移:在开发阶段可以使用
SchemaUtils.create(Users),但在生产环境,强烈建议引入 Flyway 或 Liquibase 进行版本化的数据库管理。 - 防止 SQL 注入:虽然 DSL 模式很安全,但如果你使用了拼接字符串的操作,请务必警惕。
结语
Exposed 让数据库操作变得像写集合过滤一样自然。掌握了持久化,你的全栈 App 才算真正有了“记忆”。
下一篇,我们将探讨 KMP 环境下的终极同步方案 —— SQLDelight 跨端同步:前后端共用一套 SQL。