ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SQLDelight Kotlin/JS 快速上手:用 Web Worker 驱动在浏览器中异步运行 SQLite

SQLDelight Kotlin/JS 快速上手:用 Web Worker 驱动在浏览器中异步运行 SQLite 后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载本文基于 docs/js_sqlite/index.md 整理撰写并结合仓库中drivers/web-worker-driver源码与sample-web示例项目进行纵深补充。导读SQLDelight 不仅支持 Android、JVM 与 Native 平台也支持 Kotlin/JS 浏览器目标通过web-worker-driverSQLDelight 可以与运行在 [Web Worker] 中的 SQL 实现如 SQL.js通信让所有数据库操作在后台线程中异步执行避免阻塞浏览器主线程。读完本文你将掌握在 Kotlin/JS 项目中启用generateAsync、配置 Gradle 依赖、创建 Web Worker 驱动、编写类型安全查询以及理解驱动与 Worker 之间标准化消息协议的全过程。!!! info SQLDelight 2.0 之前基于同步实现的sqljs-driver已被异步的web-worker-driver取代。启用该驱动时必须在 Gradle 配置中设置generateAsync true。核心概念为什么数据库操作要放进 Web Workerweb-worker-driver的设计目标是把所有 SQL 操作从浏览器主线程中剥离出去。它允许 SQLDelight 与一个运行在 [Web Worker] 中的 SQL 实现通信——Worker 是浏览器提供的一种可以在后台线程中执行脚本的机制。这样一来查询、事务等重活都发生在后台进程主线程只负责收发消息UI 不会因为 SQL 执行而卡顿。该驱动本身是**方言无关dialect-agnostic**的它并不绑定某个具体的 SQL 引擎而是通过一套标准化消息与 Worker 脚本通信由 Worker 端负责实际解析和执行 SQL 并回传结果。这一点可以从 WebWorkerDriver.kt 的类注释中得到印证A [SqlDriver] implementation for interacting with SQL databases running in a Web Worker. This driver is dialect-agnostic and is instead dependent on the Worker scripts implementation to handle queries and send results back from the Worker.!!! infoweb-worker-driver仅兼容浏览器browser目标不适用于 Node.js 等非浏览器环境。SQLDelight 官方附带了一个基于 [SQL.js] 的 Worker 实现sqljs.worker.js你也可以按照消息协议实现自己的 Worker。下文先介绍最快捷的官方路径再深入协议细节。第一步配置 Gradle 与生成异步数据库代码1.1 应用 SQLDelight 插件并设置generateAsync在 Kotlin/JS 工程中首先在build.gradle.kts或build.gradle中应用 SQLDelight Gradle 插件并注册数据库。关键一步是开启generateAsync让 SQLDelight 为异步驱动生成对应的挂起 API例如awaitAsList()等扩展 Kotlin DSL kotlin plugins { id(app.cash.sqldelight) version 2.x.x }repositories { google() mavenCentral() } sqldelight { databases { register(Database) { // 生成出的数据库类名 packageName.set(com.example) generateAsync.set(true) } } } Groovy DSL groovy plugins { id app.cash.sqldelight version 2.x.x }repositories { google() mavenCentral() } sqldelight { databases { register(Database) { // 生成出的数据库类名 packageName com.example generateAsync true } } } 其中版本号2.x.x请替换为你实际使用的 SQLDelight 版本仓库中的版本目录见 gradle/libs.versions.toml坐标统一为app.cash.sqldelight见 gradle.properties 中的GROUP定义。generateAsync是异步驱动的前提只有开启它SQLDelight 才会为生成的Database、Queries等类提供与异步SqlDriver配套的 API 形态配合挂起扩展使用。1.2 添加web-worker-driver与 Webpack 插件依赖在jsMain源码集加入驱动依赖。copy-webpack-plugin用于在构建产物中复制 Worker 脚本是浏览器打包时的必备配套 Kotlin DSLkotlin kotlin { sourceSets.jsMain.dependencies { implementation(app.cash.sqldelight:web-worker-driver:2.x.x) implementation(devNpm(copy-webpack-plugin, 9.1.0)) } } Groovy DSLgroovy kotlin { sourceSets.jsMain.dependencies { implementation app.cash.sqldelight:web-worker-driver:2.x.x implementation devNpm(copy-webpack-plugin, 9.1.0) } }这里devNpm表示该 npm 包只参与开发/构建期打包不会打进运行时产物。仓库中sample-web的settings.gradle见 sample-web/settings.gradle展示了web-worker-driver如何通过includeBuild与dependencySubstitution被示例工程引用可作为多工程引用驱动时的参考。第二步配置一个具体的 Web Worker2.1 引入 SQL.js 及官方 Worker 包SQLDelight 提供的官方 Worker 实现基于 SQL.js一个编译为 WebAssembly 的 SQLite。先在jsMain中同时添加 worker 包与 SQL.js 的 npm 依赖 Kotlin DSLkotlin kotlin { sourceSets.jsMain.dependencies { implementation(npm(cashapp/sqldelight-sqljs-worker, 2.x.x)) implementation(npm(sql.js, 1.8.0)) } } Groovy DSLgroovy kotlin { sourceSets.jsMain.dependencies { implementation npm(cashapp/sqldelight-sqljs-worker, 2.x.x) implementation npm(sql.js, 1.8.0) } }详细说明参见仓库文档 docs/js_sqlite/sqljs_worker.md。2.2 用 Webpack 配置复制 WASM 二进制SQL.js 包含一个 WebAssembly 二进制文件sql-wasm.wasm必须把它复制到应用构建输出中。为此在工程根目录添加一个额外的 Webpack 配置文件Kotlin/JS 会自动加载webpack.config.d/下的.js文件例如// {project}/webpack.config.d/sqljs.js config.resolve { fallback: { fs: false, path: false, crypto: false, } }; const CopyWebpackPlugin require(copy-webpack-plugin); config.plugins.push( new CopyWebpackPlugin({ patterns: [ ../../node_modules/sql.js/dist/sql-wasm.wasm ] }) );这段配置在仓库中真实存在见 sample-web/webpack.config.d/sqljs-config.js。其中resolve.fallback将 Node 内置模块fs、path、crypto关掉避免浏览器打包时报错CopyWebpackPlugin负责把sql.js的 WASM 文件复制到产物目录。!!! noteweb-worker-driver模块内的webpack.config.d/fs.js见 drivers/web-worker-driver/webpack.config.d/fs.js同样对fs等 Node 模块做了 fallback 处理这是浏览器端打包 WebAssembly 驱动时的一项通用要求。2.3 测试时的 Karma 配置运行浏览器测试时还需在工程的karma.config.d/目录下添加 Karma 配置让测试运行时能定位到 WASM 二进制。仓库中的 sample-web/karma.config.d/sqljs-config.js 给出了完整参考核心思路是把sql.js/dist/sql-wasm.wasm作为静态文件供 Karma 服务通过config.proxies[/sql-wasm.wasm]建立 URL 代理为 webpack 指定一个临时输出目录并把输出内容也加入 Karma 的files列表这是为了让 webpack 动态产物能被 Karma 识别参考自 karma-webpack 的已知问题处理方案。const path require(path); const os require(os); const dist path.resolve(../../node_modules/sql.js/dist/) const wasm path.join(dist, sql-wasm.wasm) config.files.push({ pattern: wasm, served: true, watched: false, included: false, nocache: false, }); config.proxies[/sql-wasm.wasm] path.join(/absolute/, wasm) const output { path: path.join(os.tmpdir(), _karma_webpack_) Math.floor(Math.random() * 1000000), } config.set({ webpack: {...config.webpack, output} }); config.files.push({ pattern: ${output.path}/**/*, watched: false, included: false, });第三步在代码中创建 Web Worker 驱动3.1 通过WorkerURL引用 Worker 脚本创建WebWorkerDriver时必须传入一个指向 Worker 脚本的Worker实例。Worker构造函数接受一个URL对象val driver WebWorkerDriver( Worker( js(new URL(cashapp/sqldelight-sqljs-worker/sqljs.worker.js, import.meta.url)) ) )这里的关键是 Webpack 对import.meta.url的特殊支持当URL的第二个参数是import.meta.url时Webpack 会在构建期自动解析并打包来自 npm 包cashapp/sqldelight-sqljs-worker的 Worker 脚本。!!! warning 为了让 Webpack 正确解析这个 URL必须在js()代码块中完整构造URL对象如上所示且必须带上import.meta.url参数。不要在js()外部拼接或拆开构造否则 Webpack 无法识别该 Worker 引用。该写法在仓库中有多处真实佐证JS 平台的默认工厂函数 CreateDefaultWebWorkerDriver.kt 中createDefaultWebWorkerDriver()的actual实现以及示例工程 sample-web/src/jsMain/kotlin/com/example/sqldelight/hockey/data/DbHelper.kt 中DbHelper的初始化代码都采用完全相同的new URL(..., import.meta.url)模式。从commonMain的 CreateWebWorkerDriver.kt 可以看到createDefaultWebWorkerDriver()是一个expect函数返回SqlDriver各平台各自提供actual实现——JS 平台默认就指向官方 SQL.js Worker。3.2 像普通驱动一样使用创建好驱动后WebWorkerDriver实现了 SQLDelight 的SqlDriver接口因此可以无缝用于生成的Database类及其他 SQLDelight API。它内部通过WorkerWrapper包装真实 Worker 进行消息收发查询与执行executeQuery/execute都会把 SQL 与绑定参数封装成请求消息发送给 Worker见 WebWorkerDriver.kt事务newTransaction()会向 Worker 发送begin_transactionendTransaction(successful)在成功时发送end_transaction、失败时发送rollback_transaction且支持嵌套事务见 WebWorkerDriver.kt关闭close()最终调用wrapper.terminate()终止 Worker。每次发送消息时驱动内部维护一个自增的messageCounter作为消息id用于后续匹配 Worker 的响应见 WebWorkerDriver.kt 与 WorkerWrapperRequest.kt。第四步定义并使用类型安全查询4.1 在.sq文件中编写带标签的 SQLSQLDelight 会为.sq文件中任何带标签的 SQL 语句生成类型安全函数。例如src/main/sqldelight/com/example/sqldelight/hockey/data/Player.sqselectAll: SELECT * FROM hockeyPlayer; insert: INSERT INTO hockeyPlayer(player_number, full_name) VALUES (?, ?); insertFullPlayerObject: INSERT INTO hockeyPlayer(player_number, full_name) VALUES ?;对于每条带标签语句SQLDelight 会生成一个对应的类型安全函数参数、返回类型均由 SQL 推断而来。仓库中真实的示例见 sample-web/src/jsMain/sqldelight/com/example/sqldelight/hockey/data/Player.sq——其中selectAll、insertPlayer、forTeam等标签语句展示了 JOIN、命名参数:team_id与 CAST 的用法。4.2 通过生成的 Queries 对象调用每个包含标签语句的.sq文件会生成一个 Queries 对象例如Player.sq生成PlayerQueriessuspend fun doDatabaseThings(driver: SqlDriver) { val database Database(driver) val playerQueries: PlayerQueries database.playerQueries println(playerQueries.selectAll().awaitAsList()) // [HockeyPlayer(15, Ryan Getzlaf)] playerQueries.insert(player_number 10, full_name Corey Perry) println(playerQueries.selectAll().awaitAsList()) // [HockeyPlayer(15, Ryan Getzlaf), HockeyPlayer(10, Corey Perry)] val player HockeyPlayer(10, Ronald McDonald) playerQueries.insertFullPlayerObject(player) }!!! warning 使用异步驱动时运行查询请使用挂起的awaitAs*()扩展函数如awaitAsList()、awaitAsOne()、awaitAsOneOrNull()不要使用阻塞式的executeAs*()函数。这些扩展定义在 extensions/async-extensions/src/commonMain/kotlin/app/cash/sqldelight/async/coroutines/QueryExtensions.kt 中此外 DriverExtensions.kt 还提供awaitCreate()、await()等挂起封装用于异步创建表结构Schema。Database类与关联的Schema对象由generateSqlDelightInterfaceGradle 任务生成该任务会在你编辑.sq文件时由 SQLDelight IDE 插件自动运行也会在常规 Gradle 构建中自动执行见 docs/common/index_schema.md。深入原理驱动与 Worker 的标准化消息协议web-worker-driver之所以能对接任意 SQL 实现是因为它定义了一套与方言、实现无关的消息格式。每个从驱动发往 Worker 的消息都包含一个action属性指明四类动作之一对应 WorkerAction.kt 中WorkerActions的四个常量。完整协议细节参见 docs/js_sqlite/custom_worker.md。exec指示 Worker 执行消息中附带的 SQL 语句并返回查询结果。消息携带sql要执行的 SQL与params要绑定的参数数组{ id: 5, action: exec, sql: SELECT column_a, column_b FROM some_table WHERE column_a ?;, params: [value] }begin_transaction通知 Worker 开始一个事务{ id: 2, action: begin_transaction }end_transaction通知 Worker 结束提交当前事务{ id: 3, action: end_transaction }rollback_transaction通知 Worker 回滚当前事务{ id: 8, action: rollback_transaction }响应格式id与results每条入站消息都带有一个唯一的整数idWorker 在响应中必须原样带回该id驱动据此把响应匹配到对应的请求仓库中由 WorkerWrapperRequest.kt 的id字段与 WebWorkerDriver.kt 的messageCounter机制实现。响应还包含results属性用于承载 SQL 执行结果对于查询语句results是一个数组代表结果集的行其中每一项又是一个数组代表该行的列。例如上面exec消息的响应可以是{ id: 5, results: [ [value, this is the content of column_b], [value, this is a different row] ] }对于不返回结果集的语句如 INSERT/UPDATEresults应包含单个行/列其数值代表受影响的行数{ id: 10, results: [ [1] ] }官方 SQL.js Worker 是如何实现的SQLDelight 官方提供的 Worker 脚本位于 drivers/web-worker-driver/sqljs/sqljs.worker.js它完整实现了上述协议可作为自定义 Worker 的范本通过importScripts检测是否处于 Worker 环境用initSqlJs({ locateFile: file /sql-wasm.wasm })加载 WASM 版 SQLite并创建db new SQL.Database()在self.onmessage中按action分发exec用db.exec(data.sql, data.params)执行并回传结果begin_transaction/end_transaction/rollback_transaction分别执行BEGIN TRANSACTION;/END TRANSACTION;/ROLLBACK TRANSACTION;执行出错时通过onError回传{ id: data.id, error: err }。完整示例仓库中的sample-web项目仓库中的sample-web是一个完整的 Kotlin/JS Web Worker 参考实现目录见 sample-web/包含了上述所有环节的落地代码驱动创建DbHelper在init块中用WebWorkerDriver(Worker(js(new URL(...))))创建驱动并用Mutex保证并发安全见 DbHelper.kt建表与种子数据通过HockeyDb.Schema.awaitCreate(driver)异步建表随后用生成的teamQueries/playerQueries插入球队与球员数据还演示了EnumColumnAdapter、IntColumnAdapter、FloatColumnAdapter等自定义列适配器的用法查询渲染Main.kt在协程中用playerQueries.forTeam(-1).awaitAsList()与teamQueries.selectAll().awaitAsList()取数并渲染成 HTML 表格见 Main.kt测试HockeyDbTest用runTestDbHelper跑浏览器测试验证teamsCreated、playersCreated见 HockeyDbTest.kt。注意事项与限制仅限浏览器目标web-worker-driver依赖浏览器 Web Worker API只适用于js的浏览器运行环境必须开启generateAsync忘记设置会导致生成的 API 与异步驱动不匹配这是从旧版sqljs-driver迁移到web-worker-driver时最常见的坑URL构造必须完整写在js()块内否则 Webpack 无法在构建期解析并打包 Worker 脚本WASM 文件必须随产物发布生产构建靠copy-webpack-plugin测试靠 Karma 配置二者缺一不可查询要使用挂起扩展异步驱动下请使用awaitAsList()/awaitAsOne()/awaitAsOneOrNull()不要使用阻塞式executeAs*()。从web-worker-driver的消息协议出发你还可以参照 docs/js_sqlite/custom_worker.md 实现自己的 Worker例如接入其他 SQL 引擎或自定义加密存储SQLDelight 驱动端无需任何改动——这正是该驱动方言无关、协议标准化设计带来的扩展价值。赞分享后端ORM【免费下载链接】sqldelightSQLDelight - Generates typesafe Kotlin APIs from SQL项目地址https://gitcode.com/gh_mirrors/sq/sqldelight点击查看免费下载相关推荐SQLDelight Web应用开发终极指南在浏览器中无缝运行SQLite数据库SQLDelight Web应用开发终极指南在浏览器中无缝运行SQLite数据库 SQLDelight是一款革命性的类型安全SQL工具它让开发者能够在浏览器后端ORM在浏览器与 Node.js 中运行内存版 SQLitemikro-orm/sql-js 驱动完全指南在浏览器与 Node.js 中运行内存版 SQLitemikro orm/sql js 驱动完全指南 mikro orm/sql js 是 MikroOR后端在浏览器中运行Python游戏的完整教程Pyxel Web版快速上手在浏览器中运行Python游戏的完整教程Pyxel Web版快速上手 想象一下无需安装任何软件打开浏览器就能编写和运行Python游戏Pyxel Web游戏开发上一篇终极解决方案Minecraft Photon着色器运动模糊加载失败深度排查与修复指南下一篇ComfyUI-BrushNet 图像掩码编辑功能解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表