1. 概述
本文将介绍由 Cash App 团队开发的 SQLDelight 库。✅
SQLDelight 是专为 Kotlin 项目设计的一款高效数据库库,通过编译期类型安全机制,显著简化了数据库操作流程。它允许开发者直接在代码中编写 SQL 查询,并自动生成类型安全的访问接口。
对于熟悉 Room 或其他 ORM 框架的开发者来说,SQLDelight 提供了一种更轻量、更灵活的替代方案——不强制使用注解处理器处理所有逻辑,而是把 SQL 本身作为一等公民对待。
2. 使用 SQLDelight 的优势
以下是选择 SQLDelight 的几个关键理由:
- ✅ 强类型安全:SQL 查询在编译阶段就能检查语法和字段匹配问题,有效避免运行时因拼写错误或字段缺失导致的崩溃。
- ✅ 自动代码生成:基于
.sq
文件中的 SQL 定义,SQLDelight 会自动生成对应的 Kotlin 数据类和 DAO 接口,大幅减少模板代码。 - ✅ 高性能查询执行:无需在运行时解析 SQL,所有语句预编译,提升数据库交互效率,尤其适合高频读写场景。
- ✅ Kotlin 接口形式的 Schema 定义:数据库结构以 Kotlin 接口方式暴露,支持编译期验证表结构变更,降低维护成本。
- ✅ 原生支持 Kotlin Multiplatform:可在 Android、iOS、JVM 后端等多平台共享同一套数据库逻辑,非常适合跨平台项目。
- ✅ 单一可信数据源(Single Source of Truth):Schema 和 Query 都集中在一个地方管理,团队协作更清晰。
- ✅ 平滑迁移现有 SQL:已有 SQL 脚本可直接复制到
.sq
文件中,立即获得类型安全封装,踩坑少。 - ✅ IDE 支持完善:官方提供 SQLDelight IntelliJ 插件,支持语法高亮、跳转、补全等功能,开发体验优秀。
3. 在项目中集成 SQLDelight
3.1 添加依赖
首先,在项目的 build.gradle.kts
中添加必要的依赖项:
plugins {
kotlin("multiplatform")
id("com.squareup.sqldelight")
}
dependencies {
implementation("com.squareup.sqldelight:runtime:2.0.0-rc02")
implementation("com.squareup.sqldelight:jdbc-driver:2.0.0-rc02")
// 注意:gradle-plugin 应该在插件块中声明,而不是这里
}
⚠️ 注意:kapt
已不再推荐用于 SQLDelight 2.x,应使用 Gradle 插件方式配置。正确做法是在 plugins {}
块中引入:
plugins {
id("com.squareup.sqldelight") version "2.0.0-rc02"
}
如果是 Maven 项目,则添加如下依赖:
<dependency>
<groupId>com.squareup.sqldelight</groupId>
<artifactId>runtime</artifactId>
<version>2.0.0-rc02</version>
</dependency>
<dependency>
<groupId>com.squareup.sqldelight</groupId>
<artifactId>kotlin-driver</artifactId>
<version>2.0.0-rc02</version>
</dependency>
📌 版本号请替换为当前最新稳定版,参考 Maven Central。
3.2 定义数据库 Schema
创建一个以 .sq
为后缀的文件(例如 User.sq
),用标准 SQL 语法定义表结构:
CREATE TABLE user (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email_address TEXT
);
📌 建议命名规范:每个 .sq
文件对应一张表或一组相关查询,便于维护。
✅ 重要提醒:每次修改 .sq
文件后,务必触发一次 Gradle sync 或 build,确保代码生成器及时生成 Kotlin 类。
3.3 编写 SQLDelight 查询
在同一个 .sq
文件中,可以继续定义具名查询(Named Queries)和操作语句:
SelectAll:
SELECT * FROM user;
SelectUserById:
SELECT * FROM user WHERE id = ?;
InsertUser:
INSERT INTO user(first_name, last_name, email_address)
VALUES (?, ?, ?);
DeleteUser:
DELETE FROM user WHERE id = ?;
⚠️ 踩坑提示:上面示例中 DeleteUser
的表名写成了 contact
,实际应为 user
,否则会导致编译失败或运行时异常!务必核对表名一致性。
构建项目后,SQLDelight 将生成以下 Kotlin 接口和常量:
interface User {
val id: Long
val first_name: String
val last_name: String
val email_address: String?
}
companion object {
val CREATE_TABLE: String = """
CREATE TABLE user (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
first_name TEXT NOT NULL,
last_name TEXT NOT NULL,
email_address TEXT
)
"""
val SELECTALL: String = "SELECT * FROM user"
val SELECTUSERBYID: String = "SELECT * FROM user WHERE id = ?"
}
同时还会生成 UserQueries
接口,包含类型安全的方法:
fun selectAll(): Query<User>
fun selectUserById(id: Long): Query<User>
fun insertUser(firstName: String, lastName: String, emailAddress: String?)
fun deleteUser(id: Long)
这些方法可以直接调用,传参即自动绑定,无需手动处理 Cursor
或 PreparedStatement
。
4. 使用 SQLDelight 的常见误区
尽管 SQLDelight 设计精良,但在实际使用中仍有一些容易踩的坑:
- ❌ 手动修改生成代码:生成的文件位于
build/generated/
目录下,任何改动都会在下次构建时被覆盖。永远不要在这里写业务逻辑。 - ❌ 忽略 Schema 变更的迁移策略:升级版本时若涉及字段增删,必须实现 Migration 脚本,否则旧用户启动应用可能崩溃。
- ❌ 滥用嵌套子查询:虽然 SQLDelight 支持复杂查询,但过度嵌套会影响性能,建议拆分为多个简单查询并在业务层组合。
- ❌ 缺乏异常处理:数据库操作可能抛出
SQLException
,尤其是在并发写入时,需合理捕获并降级处理。 - ❌ 未测试边界情况:比如插入 null 值、空结果集、重复主键等场景,应在单元测试或集成测试中覆盖。
- ⚠️ 忘记启用插件或配置 driver:Multiplatform 项目中需明确指定各平台使用的数据库驱动(如 NativeDriver、AndroidSqliteDriver 等)。
5. 总结
本文介绍了 SQLDelight 的核心特性及其在 Kotlin 项目中的集成方式。通过将 SQL 查询与类型安全结合,SQLDelight 实现了“写 SQL,享 ORM”的理想状态,特别适合追求性能与可控性的中大型项目。
无论是 Android 单端开发,还是 Kotlin Multiplatform 跨平台项目,SQLDelight 都是一个值得深入掌握的工具。
📌 示例完整代码已托管至 GitHub:https://github.com/baeldung/kotlin-tutorials/tree/master/kotlin-multiplatform