- 后端
- ORM
【免费下载链接】sqldelight
SQLDelight - Generates typesafe Kotlin APIs from SQL
导读
本文聚焦 SQLDelight 在 JVM 场景下的 Gradle 构建配置:如何通过sqldelightGradle DSL 显式声明数据库、配置包名、方言、迁移验证与代码生成选项,并在此基础上结合当前仓库中的 HSQL 方言实现与 JDBC 驱动源码,说明 JVM 数据库(如 H2、HSQLDB)如何与 SQLDelight 生成的类型安全 Kotlin API 衔接。读完本文,你将掌握从零配置一个 JVM + HSQL 数据库工程、控制代码生成行为、并利用 schema 依赖与迁移校验保证数据库演进质量的一整套方案。
说明:
docs/jvm_h2/gradle.md与docs/jvm_mysql/gradle.md、docs/jvm_postgresql/gradle.md、docs/jvm_sqlite/gradle.md等 JVM 分支文档共用同一份公共 Gradle 配置文档 docs/common/gradle.md,本文内容即基于该公共文档展开,并结合仓库源码进行补充。
一、为什么需要显式声明数据库:Gradle DSL 的作用
SQLDelight 插件会在应用 Kotlin/JVM 插件后自动寻找.sq与.sqm文件并生成代码;但当项目需要更精细的控制——例如指定方言、多个数据库、schema 依赖或迁移输出目录——就需要通过 Gradle DSL 显式声明数据库。
DSL 的顶层入口是sqldelight扩展块,其核心结构如下:
=== "Kotlin(build.gradle.kts)"kotlin sqldelight { databases { create("MyDatabase") { // Database configuration here. } } }
=== "Groovy(build.gradle)"groovy sqldelight { databases { MyDatabase { // Database configuration here. } } }
databases是数据库的容器,create("MyDatabase")(Kotlin DSL)或MyDatabase { }(Groovy DSL)用于声明一个名为MyDatabase的数据库,其生成的数据库类名即MyDatabase。下方的所有数据库级配置项都写在create块内部。
二、SQLDelight 扩展级配置:linkSqlite
linkSqlite是扩展(sqldelight块)级别的配置项,作用于整个模块。
- 类型:
Property<Boolean> - 默认值:
true
该选项仅对native 目标生效,用于决定是否自动链接 sqlite。它会为项目编译成动态 framework(这是近期 Kotlin Multiplatform 版本的默认行为)时添加链接 sqlite 所需的元数据。需要注意:
- 对于静态 framework,该标志无效;导入项目的 Xcode 构建应自行在链接器参数中加入
-lsqlite3; - 也可以通过 CocoaPods 插件为项目添加
sqlite3pod 依赖,或在 podspec 的spec.libraries设置中加入sqlite3,例如 Kotlin DSL 写法:extraSpecAttributes["libraries"] = "'c++', 'sqlite3'"。
由于linkSqlite只影响 native 链接元数据,纯 JVM 项目(H2/HSQL)中通常保持默认值即可,无需修改。
=== "Kotlin"kotlin linkSqlite.set(true)=== "Groovy"groovy linkSqlite = true
三、数据库级配置项详解
3.1packageName:生成的数据库类所在包
- 类型:
Property<String>
指定生成的数据库类(以及相关查询接口)所在的 Kotlin 包名。这是每个数据库必填的核心配置,后续在应用代码中通过该包名 import 生成的MyDatabase类。
=== "Kotlin"kotlin packageName.set("com.example.db")=== "Groovy"groovy packageName = "com.example.db"
3.2srcDirs:.sq / .sqm 文件的扫描目录
- 类型:
ConfigurableFileCollection - 默认值:
src/[prefix]main/sqldelight,其中[prefix]取决于所应用的 Kotlin 插件,例如 multiplatform 工程为common(即src/commonMain/sqldelight),纯 JVM 工程为src/main/sqldelight。
srcDirs定义插件搜索.sq(schema 与查询定义)和.sqm(迁移文件)的目录集合。除了setFrom(...)赋值方式,还提供变体srcDirs(vararg objects: Any)直接传入多个路径:
=== "Kotlin"kotlin srcDirs.setFrom("src/main/sqldelight") // 或一次传入多个目录 srcDirs("src/main/sqldelight", "main/sqldelight")=== "Groovy"groovy srcDirs = ['src/main/sqldelight'] // 或一次传入多个目录 srcDirs('src/main/sqldelight', 'main/sqldelight')
仓库中的 JVM 集成测试可以印证这一目录约定:例如 HSQL 集成测试工程 integration-hsql/src/main/sqldelight 将Characters.sq、Dog.sq放在src/main/sqldelight下,插件按默认规则即可扫描到。
3.3schemaOutputDirectory:迁移校验的 schema 输出目录
- 类型:
DirectoryProperty - 默认值:
null
指定.dbschema 文件(当前最新 schema 的导出)的存放目录(相对于项目根目录)。这些文件用于校验"迁移链最终得到的数据库与最新 schema 一致"。当该值为null时,不会创建迁移校验任务。
=== "Kotlin"kotlin schemaOutputDirectory.set(file("src/main/sqldelight/databases"))=== "Groovy"groovy schemaOutputDirectory = file("src/main/sqldelight/databases")
3.4dependency:模块间 schema 依赖
- 类型:
Project
可选地指定对其他 Gradle 工程的 schema 依赖(详见 Schema Dependencies)。
=== "Kotlin"kotlin dependency(project(":other-project"))=== "Groovy"groovy dependency project(":other-project")
3.5dialect:选择目标 SQL 方言
- 类型:
String或Provider<MinimalExternalModuleDependency>
方言通过 Gradle 依赖选择,依赖坐标形式为app.cash.sqldelight:{dialect module}:{{ versions.sqldelight }}。
方言选择规则:
- Android 项目会根据
minSdk自动选择对应的 SQLite 版本; - 其他情况默认使用 SQLite 3.18;
- JVM 数据库(H2/HSQL/MySQL/PostgreSQL)则需显式指定对应方言。
当前仓库 dialects 目录下提供的方言模块:
| 数据库 | 方言模块(artifactId) |
|---|---|
| HSQL | hsql-dialect |
| MySQL | mysql-dialect |
| PostgreSQL | postgresql-dialect |
| SQLite 3.18 | sqlite-3-18-dialect |
| SQLite 3.24 | sqlite-3-24-dialect |
| SQLite 3.25 | sqlite-3-25-dialect |
| SQLite 3.30 | sqlite-3-30-dialect |
| SQLite 3.33 | sqlite-3-33-dialect |
| SQLite 3.35 | sqlite-3-35-dialect |
| SQLite 3.37 | sqlite-3-37-dialect |
| SQLite 3.38 | sqlite-3-38-dialect |
| SQLite 3.39 | sqlite-3-39-dialect |
| SQLite 3.44 | sqlite-3-44-dialect |
配置示例(以 SQLite 3.24 为例,其他方言替换 artifactId 即可):
=== "Kotlin"kotlin dialect("app.cash.sqldelight:sqlite-3-24-dialect:{{ versions.sqldelight }}")=== "Groovy"groovy dialect 'app.cash.sqldelight:sqlite-3-24-dialect:{{ versions.sqldelight }}'
HSQL 方言的源码印证:HsqlDialect.kt 实现了SqlDelightDialect接口,其runtimeTypes指向 JDBC 驱动的JdbcCursor与JdbcPreparedStatement,并将asyncRuntimeTypes声明为不支持(throw UnsupportedOperationException("HSQL does not support an async driver")),说明 HSQL 方言当前不支持异步驱动。同时其类型解析由 HsqlTypeResolver.kt 完成,例如将TINYINT/SMALLINT/INTEGER/BIGINT映射为对应 HSQL 整数类型、CHARACTER/VARCHAR映射为TEXT、DATE映射为TEXT、BIT/BINARY映射为BLOB等;COALESCE、IFNULL、GREATEST、LEAST、MAX、MIN、LENGTH等函数也有专门的类型推断逻辑。
3.6verifyMigrations:构建期迁移校验
- 类型:
Property<Boolean> - 默认值:
false
设为true后,迁移(.sqm)文件中的任何错误都会导致构建失败,从而把迁移问题暴露在 CI 阶段而非运行时。
=== "Kotlin"kotlin verifyMigrations.set(true)=== "Groovy"groovy verifyMigrations = true
3.7treatNullAsUnknownForEquality:NULL 等值比较语义
- 类型:
Property<Boolean> - 默认值:
false
设为true后,SQLDelight 在使用IS进行等值比较时,不会把可空类型的值替换为等值比较(即遵循 SQL 三值逻辑中"NULL 视为未知"的语义)。
=== "Kotlin"kotlin treatNullAsUnknownForEquality.set(true)=== "Groovy"groovy treatNullAsUnknownForEquality = true
3.8generateAsync:为异步驱动生成挂起查询方法
- 类型:
Property<Boolean> - 默认值:
false
设为true后,SQLDelight 会为查询生成suspend挂起方法,供异步驱动使用。需要注意:HSQL 方言在源码中明确不支持异步驱动(见上文 HsqlDialect.kt),因此 HSQL 场景下应保持false;如需异步能力可考虑 MySQL/PostgreSQL 的异步支持方案。
=== "Kotlin"kotlin generateAsync.set(true)=== "Groovy"groovy generateAsync = true
3.9deriveSchemaFromMigrations:从迁移文件推导 schema
- 类型:
Property<Boolean> - 默认值:
false
设为true时,数据库 schema 由.sqm迁移文件按顺序应用后推导得出;为false时,schema 由.sq文件定义。
=== "Kotlin"kotlin deriveSchemaFromMigrations.set(true)=== "Groovy"groovy deriveSchemaFromMigrations = true
3.10expandSelectStar:展开 SELECT * 投影
- 类型:
Property<Boolean> - 默认值:
true
设为true时,SQLDelight 会把SELECT *重写为显式列出实际结果列的语句。例如:
CREATE TABLE hockey_player ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, number INTEGER NOT NULL ); getAll: SELECT * FROM hockey_player;会被重写为:
SELECT hockey_player.id, hockey_player.name, hockey_player.number FROM hockey_player;这保证了生成结果类的列顺序稳定、投影列显式化。
=== "Kotlin"kotlin expandSelectStar.set(true)=== "Groovy"groovy expandSelectStar = true
3.11codegenExcludedColumns:从代码生成中排除列
- 类型:
SetProperty<String> - 默认值:空集合
该配置接收一组表名.列名(大小写必须与 SQLDelight schema 源一致),将这些列从生成的模型类和展开后的SELECT *投影中省略。它只影响代码生成,不改变 SQL schema 或迁移输出。
典型用途:在后续 schema 迁移真正删除某列之前,先用它提前更新生成的 Kotlin API。约束与注意事项:
- 若配置的表或列不存在,或某个 model 绑定 insert、
SELECT结果列、RETURNING子句显式引用了被排除的列,SQLDelight 会使编译失败; - 由于这只是 codegen 层面的排除,应用层需自行保证在物理删列之前,仍存在的被排除列可以在写入时省略(例如使用可空列或默认值);
- 如果
.sq文件包含CREATE TABLEschema 定义,应保留被排除列在 schema 定义中,直到物理 schema 迁移真正删除它;同时移除对该列的显式查询引用,但让 schema 源继续反映当前数据库形态。
=== "Kotlin"kotlin codegenExcludedColumns.set(setOf("hockey_player.number"))=== "Groovy"groovy codegenExcludedColumns = ["hockey_player.number"]
四、Schema 依赖:跨模块共享 schema
dependency配置用于让当前数据库引入另一个 Gradle 工程的 schema。典型场景是分层模块:上层模块(ProjectA)依赖下层模块(ProjectB)的数据库 schema 进行编译。
=== "Kotlin" ```kotlin // project-a/build.gradle.kts
sqldelight { databases { create("MyDatabase") { packageName.set("com.example.projecta") dependency(project(":ProjectB")) } } } ```=== "Groovy" ```groovy // project-a/build.gradle
sqldelight { databases { MyDatabase { packageName = "com.example.projecta" dependency project(":ProjectB") } } } ```此时 SQLDelight 会在ProjectB中查找同名数据库(MyDatabase)并把它的 schema 纳入编译。为使依赖解析成功,ProjectB 必须声明同名数据库,但生成到不同包:
=== "Kotlin" ```kotlin // project-b/build.gradle.kts
sqldelight { databases { // Same database name create("MyDatabase") { package = "com.example.projectb" } } } ```=== "Groovy" ```groovy // project-b/build.gradle
sqldelight { databases { // Same database name MyDatabase { package = "com.example.projectb" } } } ```重要限制:如果使用了deriveSchemaFromMigrations = true,那么依赖此模块的每个模块也必须启用该功能,以保证迁移推导出的 schema 在各模块间一致。
五、JVM 场景实战:HSQL 数据库的完整配置与运行
5.1 完整 build.gradle.kts 示例
将上述配置综合到 JVM + HSQL 工程中,一个可用的构建脚本如下:
plugins { kotlin("jvm") id("app.cash.sqldelight") } repositories { mavenCentral() } dependencies { implementation("app.cash.sqldelight:jdbc-driver:{{ versions.sqldelight }}") implementation("org.hsqldb:hsqldb:2.7.2") } sqldelight { databases { create("MyDatabase") { packageName.set("com.example.db") dialect("app.cash.sqldelight:hsql-dialect:{{ versions.sqldelight }}") // 开启迁移校验(需要先配置 schema 输出目录) schemaOutputDirectory.set(file("src/main/sqldelight/databases")) verifyMigrations.set(true) } } }要点拆解:
jdbc-driver依赖提供 JdbcDriver.kt 中的JdbcDriver抽象类与DataSource.asJdbcDriver()扩展;hsql-dialect依赖让解析器/代码生成器理解 HSQL 语法与类型;org.hsqldb:hsqldb是实际的 JDBC 驱动实现,用于建立连接。
5.2 在代码中使用生成的数据库与 JDBC 驱动
HSQL 是标准 JDBC 数据库,SQLDelight 通过JdbcDriver抽象与之衔接。仓库的 HSQL 集成测试 HsqlTest.kt 给出了完整可运行模式:
val conn = DriverManager.getConnection("jdbc:hsqldb:mem:mymemdb;shutdown=true") val driver = object : JdbcDriver() { override fun getConnection() = conn override fun closeConnection(connection: Connection) = Unit override fun addListener(vararg queryKeys: String, listener: Query.Listener) = Unit override fun removeListener(vararg queryKeys: String, listener: Query.Listener) = Unit override fun notifyListeners(vararg queryKeys: String) = Unit } val database = MyDatabase(driver) @Before fun before() { MyDatabase.Schema.create(driver) }使用要点:
- 通过
DriverManager.getConnection("jdbc:hsqldb:mem:mymemdb;shutdown=true")创建内存库连接; - 匿名子类化
JdbcDriver,覆写getConnection/closeConnection与监听器方法(JDBC 驱动默认不支持查询监听,覆写为 No-op 即可); MyDatabase(driver)构造数据库实例,MyDatabase.Schema.create(driver)负责建表;- 之后即可调用
database.dogQueries.insertDog(...)、database.dogQueries.selectDogs().executeAsOne()等类型安全 API。
从 JdbcDriver.kt 源码可见底层机制:execute/executeQuery通过connectionAndClose()获取连接,将 SQL 交给java.sql.PreparedStatement执行;JdbcPreparedStatement负责把 Kotlin 类型(String/Long/Double/ByteArray/Boolean 等)绑定为 JDBC 类型,JdbcCursor负责把ResultSet各列读回 Kotlin 类型。此外驱动对事务要求连接默认处于autoCommit = true,beginTransaction会显式校验这一点(见check(autoCommit)逻辑),这也是自定义JdbcDriver.getConnection()时需要注意的约定。
5.3 非内存数据库与 DataSource 方式
除内存库外,也可连接文件型 HSQL 库(jdbc:hsqldb:file:/path/to/db),或通过javax.sql.DataSource注入连接池,此时可直接使用扩展函数:
import app.cash.sqldelight.driver.jdbc.asJdbcDriver val dataSource: DataSource = ... val database = MyDatabase(dataSource.asJdbcDriver())asJdbcDriver()在 JdbcDriver.kt 中实现,默认把监听器方法实现为 No-op。
六、迁移与 schema 管理:让数据库演进可控
JVM 工程的数据库演进同样遵循 SQLDelight 的迁移体系:
- 在
srcDirs(如src/main/sqldelight)中编写1.sqm、2.sqm…… 递增的迁移文件; - 设置
schemaOutputDirectory导出最新 schema 的.db文件; - 设置
verifyMigrations = true,使任何迁移错误在构建期直接失败; - 如需以迁移为唯一 schema 来源,设置
deriveSchemaFromMigrations = true(此时依赖方也必须同步开启)。
仓库中integration-hsql之外,sqldelight-gradle-plugin/src/test 下还包含大量迁移验证场景(如migration-success、migration-failure、migration-gap-failure、migration-squash、schema-output等目录),展示了从成功/失败/缺口/压缩迁移到 schema 输出的各种组合,可作为配置与预期行为的参考样例。
七、常见问题与注意事项
- HSQL 方言处于实验阶段:
docs/jvm_h2/index.md明确提示 HSQL 支持正在孵化,方言的部分语法仍不完整;遇到不支持的语法时可向 sql-psi 反馈。因此生产使用前应充分验证目标 SQL 语句。 - HSQL 不支持异步驱动:
HsqlDialect的asyncRuntimeTypes直接抛出UnsupportedOperationException,设置generateAsync = true无法用于 HSQL。 expandSelectStar默认开启:如果希望保留SELECT *的原始形态,需显式设置expandSelectStar.set(false)。codegenExcludedColumns只影响代码生成:它不修改 SQL schema,也不会改变迁移输出;删列仍需通过.sqm迁移完成,且在此之前应用层要保证写入可省略该列。- schema 依赖双方包名必须不同:
dependency(project(":B"))要求 B 中同名数据库生成到不同包,否则会造成类冲突。 deriveSchemaFromMigrations需要全链路开启:任一依赖方未开启都会导致 schema 推导不一致。
八、小结
本文围绕 SQLDelight 的 Gradle DSL,完整梳理了扩展级与数据库级的所有配置项,包括packageName、srcDirs、dialect、schemaOutputDirectory、verifyMigrations、deriveSchemaFromMigrations、expandSelectStar、codegenExcludedColumns等,并结合仓库源码说明了 JVM + HSQL 场景下的完整落地路径:选择hsql-dialect、接入jdbc-driver、通过JdbcDriver子类化或DataSource.asJdbcDriver()连接 HSQLDB,最后配合迁移校验体系保障 schema 演进安全。读者可参考 integration-hsql 测试工程与 docs/jvm_h2 文档目录,在真实工程中验证上述配置。
- 后端
- ORM
【免费下载链接】sqldelight
SQLDelight - Generates typesafe Kotlin APIs from SQL
相关推荐
SQLDelight 在 JVM 上使用 MySQL:从 Gradle 配置到类型安全查询的完整指南
SQLDelight 在 JVM 上使用 MySQL:从 Gradle 配置到类型安全查询的完整指南 本指南以 SQLDelight 仓库中的 JVM + My
后端ORMCloudflare Workers 兼容性标志详解:完全遵循 WHATWG 标准的新 URL 解析实现
Cloudflare Workers 兼容性标志详解:完全遵循 WHATWG 标准的新 URL 解析实现 本文聚焦 Cloudflare Workers 运行时
后端ORMPLFM_RADAR:开源 10.5 GHz 相控阵雷达,硬件与固件全链路公开
PLFM_RADAR:开源 10.5 GHz 相控阵雷达,硬件与固件全链路公开 雷达是"听得多、摸得少"的领域:成品系统价格高,源码通常不公开。PLFM_RAD
后端ORM
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考