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)

这些方法可以直接调用,传参即自动绑定,无需手动处理 CursorPreparedStatement


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


原始标题:A Guide to SQLDelight