当前位置: 首页 > news >正文

Kotlin 跨平台 SqliteNow 全平台数据持久化方案

Kotlin 跨平台 SqliteNow 全平台数据持久化方案

    • 1. 环境与依赖配置
      • 1.0 创建一个Kotlin 多平台项目
      • 1.1 版本声明(libs.versions.toml)
      • 1.2 项目级插件配置(build.gradle.kts)
      • 1.3 模块级依赖配置(app/shared/build.gradle.kts)
      • 1.4 Web 平台配置(Webpack)
      • 1.5 WasmJs 平台资源复制配置
    • 2 创建数据库
      • 2.0 添加数据库构建脚本
      • 2.1 添加学生的Kotlin类
      • 2.2 添加学生表和基础查询语句
      • 2.3 测试你的数据库
    • 3. 测试数据库
      • 3.0 在JVM平台测试数据库
      • 3.1 在Web Js平台测试数据库
      • 3.2 在Web Wasm平台测试数据库
      • 3.3 Web平台常见错误修复

SqliteNow 是一款面向 Kotlin Multiplatform(KMP)的全平台 SQLite 持久化库,它通过编译时 SQL 验证、类型安全的查询构建器以及跨平台(Android、iOS、Desktop、Web)的统一 API,为 KMP 项目提供了高效、可靠的数据存储解决方案。本文将以一个学生信息管理为例,详细介绍如何从零开始集成 SqliteNow,完成表结构定义、数据操作,并在 JVM(Desktop)与 Web(JS/Wasm)平台上验证数据持久化效果。

项目源码
本文涉及的多平台库源地址可在 GitHub - sqlitenow-kmp 查看。
详情教程可在 GitHub sqlitenow-kmp wiki 查看。

1. 环境与依赖配置

1.0 创建一个Kotlin 多平台项目

IntelliJ IDEA 2026.1.2中创建一个Kotlin多平台项目, 项目目录如下:

1.1 版本声明(libs.versions.toml)

首先在项目的gradle/libs.versions.toml中声明所需库的版本:

[versions] android-minSdk = "26" # sqlitenow 需要最低SDK版本 26 kotlin = "2.3.21" sqlite = "2.6.2" sqlitenow = "0.9.1" kotlinx-datetime = "0.8.0" kotlinx-serialization-json = "1.11.0" [libraries] sqlite-bundled = { module = "androidx.sqlite:sqlite-bundled", version.ref = "sqlite" } sqlitenow-core = { module = "dev.goquick.sqlitenow:core", version.ref = "sqlitenow" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization-json" } [plugins] sqlitenow = { id = "dev.goquick.sqlitenow", version.ref = "sqlitenow" } kotlinSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }

1.2 项目级插件配置(build.gradle.kts)

在项目根目录的build.gradle.kts中声明插件(apply false 表示不在根项目直接应用):

plugins{alias(libs.plugins.sqlitenow)applyfalsealias(libs.plugins.kotlinSerialization)applyfalse}

1.3 模块级依赖配置(app/shared/build.gradle.kts)

在共享模块app/sharedbuild.gradle.kts中应用插件并配置平台特定依赖, 然后同步项目:

plugins{alias(libs.plugins.sqlitenow)alias(libs.plugins.kotlinSerialization)}kotlin{sourceSets{androidMain.dependencies{implementation(libs.sqlite.bundled)}iosMain.dependencies{implementation(libs.sqlite.bundled)}jvmMain.dependencies{implementation(libs.sqlite.bundled)}commonMain.dependencies{implementation(libs.sqlitenow.core)implementation(libs.kotlinx.datetime)implementation(libs.kotlinx.serialization.json)}}}

1.4 Web 平台配置(Webpack)

对于 Web 平台(JS 目标),需要在app/webApp模块中配置 Webpack,以正确处理 SQL.js 的 WASM 资源。创建webpack.config.d/sqljs.js

config.resolve={fallback:{fs:false,path:false,crypto:false,}};config.module=config.module||{};config.module.rules=Array.isArray(config.module.rules)?config.module.rules:[];config.module.rules.push({test:/sql-wasm\.wasm$/,type:"asset/resource",});constCopyWebpackPlugin=require('copy-webpack-plugin');config.plugins.push(newCopyWebpackPlugin({patterns:['../../node_modules/sql.js/dist/sql-wasm.wasm']}));

1.5 WasmJs 平台资源复制配置

对于 WebAssembly(WasmJs)目标,需在共享模块的构建脚本中添加资源复制任务,确保 WASM 文件能被正确打包。在app/shared/build.gradle.ktskotlin块后添加:

kotlin{sourceSets{webMain.dependencies{implementation(devNpm("copy-webpack-plugin","11.0.0"))implementation(npm("sql.js","1.13.0"))}}}tasks.named<ProcessResources>("wasmJsProcessResources"){valrootBuildDir=rootProject.layout.buildDirectoryvalsqlitenowPath="wasm/node_modules/sqlitenow-kmp-library-core"valmoveFileList=listOf("sql-wasm.wasm","sqlitenow-sqljs.js","sqlitenow-indexeddb.js")moveFileList.forEach{fileName->from(rootBuildDir.file("$sqlitenowPath/$fileName"))}duplicatesStrategy=DuplicatesStrategy.INCLUDE}

构建完成后检查app/shared/build/processedResources/wasmJs/main中是否包含以下三个文件sql-wasm.wasm,sqlitenow-sqljs.js,sqlitenow-indexeddb.js

2 创建数据库

2.0 添加数据库构建脚本

app/shared模块级别build.gradle.kts的中添加数据库构建脚本

sqliteNow{databases{create("AppDatabase"){packageName.set("com.example.sqliteNow")debug=false}}}

2.1 添加学生的Kotlin类

app/shared/commonMain中创建sql目录, 然后在该目录下创建AppDatabase目录(与构建脚本中名称相同),最后在AppDatabase目录下, 创建schemaqueries目录

app/shared/commonMain中创建Student.kt文件, 表示数据库中的学生实例

packageorg.example.kmpsqlitenowdemoimportkotlinx.datetime.LocalDateimportkotlinx.serialization.Serializable@SerializableenumclassGender{MALE,FEMALE}dataclassStudent(valid:Long=0,valname:String,valage:Int,valgender:Gender,valbirthDate:LocalDate)

2.2 添加学生表和基础查询语句

schema目录中, 创建一个学生表student.sql

CREATETABLEIFNOTEXISTSstudent(idINTEGERPRIMARYKEYNOTNULL,nameTEXTNOTNULL,ageINTEGERNOTNULL,-- @@{field=gender, propertyType=org.example.kmpsqlitenowdemo.Gender, adapter=custom}genderTEXTNOTNULL,-- @@{field=birthDate, propertyType=kotlinx.datetime.LocalDate, adapter=custom}birthDateTEXTNOTNULL);

queries目录中创建student目录,然后创建一个查询全部语句selectAll.sql

-- @@{ queryResult=StudentEntity }SELECT*FROMstudent;

queries/student目录中创建一个添加学生的语句add.sql

INSERTINTOstudent(name,age,gender,birthDate)VALUES(:name,:age,:gender,:birthDate);

最后同步项目,运行 Gradle 任务生成数据库代码:./gradlew :app:shared:generateAppDatabase

2.3 测试你的数据库

app/shared/commonMain中创建testDatabase.kt文件, 用于测试数据库, 如果没有学生George, 那么会添加, 然后输出学生表

packageorg.example.kmpsqlitenowdemoimportcom.example.sqliteNow.AppDatabaseimportcom.example.sqliteNow.StudentQueryimportcom.example.sqliteNow.VersionBasedDatabaseMigrationsimportdev.goquick.sqlitenow.common.resolveDatabasePathimportdev.goquick.sqlitenow.core.util.fromSqliteDateimportdev.goquick.sqlitenow.core.util.toSqliteDateimportkotlinx.coroutines.delayimportkotlinx.datetime.LocalDateimportkotlinx.serialization.json.Jsonimportkotlin.time.Duration.Companion.millisecondssuspendfuntestDatabase(){valdb=AppDatabase(dbName=resolveDatabasePath(dbName="database.db",appName="KmpSqliteNowDemo"),migration=VersionBasedDatabaseMigrations(),studentAdapters=AppDatabase.StudentAdapters(genderToSqlValue={gender->Json.encodeToString(gender)},sqlValueToGender={string->Json.decodeFromString(string)},birthDateToSqlValue={localDate->localDate.toSqliteDate()},sqlValueToBirthDate={string->LocalDate.fromSqliteDate(string)}))db.open()println("[Database] opened")valstudentList=db.student.selectAll.asList()println("[Database] studentList:$studentList")if(studentList.find{it.name=="George"}==null){valparams=StudentQuery.Add.Params(name="George",age=19,gender=Gender.MALE,birthDate=LocalDate(year=2003,month=7,day=27))db.student.add(params)println("[Database] add George successful")}valnowStudentList=db.student.selectAll.asList()println("[Database] nowStudentList:$nowStudentList")delay(1000.milliseconds)db.close()println("[Database] closed")}

app/shared/commonMainApp.kt文件的App函数下, 添加下面代码, 用于测试数据库

funApp(){LaunchedEffect(Unit){testDatabase()}// ...}

3. 测试数据库

3.0 在JVM平台测试数据库

然后先在desktop平台运行一下, 会产生以下输出

[Database] opened [Database] studentList: [] [Database] add George successful [Database] nowStudentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] closed

重新运行输出结果如下, 说明学生George成功添加到了数据库中, 完成了持久化

[Database] opened [Database] studentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] nowStudentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] closed

3.1 在Web Js平台测试数据库

在web Js平台也运行一下

[SqliteNow][OPFS] Using Origin Private File System persistence for database.db [SqliteNow] No persisted snapshot for database.db [SqlJs] loading sql.js module… [SqlJs] sql.js module loaded [SqlJs] Opening database.db with empty in-memory database [Database] opened [Database] studentList: [] [Database] add George successful [Database] nowStudentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] closed

重新运行结果如下

[SqliteNow][OPFS] Using Origin Private File System persistence for database.db [SqliteNow] Restored 8192 bytes for database.db [SqlJs] loading sql.js module… [SqlJs] sql.js module loaded [SqlJs] Opening database.db from persisted snapshot (8192 bytes) [Database] opened [Database] studentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] nowStudentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] closed

说明学生George成功添加到了数据库中, 完成了持久化

3.2 在Web Wasm平台测试数据库

在web WasmJs平台也运行一下

(sqlitenow) [sqlitenow] [SqliteNow][IndexedDB] Using IndexedDB persistence for database.db [SqliteNow] No persisted snapshot for database.db (sqlitenow) [sqlitenow] [SqlJs][Wasm] Loading sql.js module… (sqlitenow) [sqlitenow] [SqlJs][Wasm] sql.js module ready (sqlitenow) [sqlitenow] [SqlJs][Wasm] Kotlin opening database.db without snapshot (new database) [Database] opened [Database] studentList: [] [Database] add George successful [Database] nowStudentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] closed

重新运行结果如下

(sqlitenow) [sqlitenow] [SqliteNow][IndexedDB] Using IndexedDB persistence for database.db [SqliteNow] Restored 8192 bytes for database.db (sqlitenow) [sqlitenow] [SqlJs][Wasm] Loading sql.js module… (sqlitenow) [sqlitenow] [SqlJs][Wasm] sql.js module ready (sqlitenow) [sqlitenow] [SqlJs][Wasm] Kotlin opening database.db with snapshot bytes=8192 [Database] opened [Database] studentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] nowStudentList: [StudentEntity(id=1, name=George, age=19, gender=MALE, birthDate=2003-07-27)] [Database] closed

说明学生George成功添加到了数据库中, 完成了持久化

3.3 Web平台常见错误修复

如果在web平台中出现,打开数据库后找不到student表的错误,或者表格字段错误等等,请在浏览器设置中,清除浏览数据在尝试,这将清除你在开发环境浏览器中保留的IndexedDB


如果Web Wasm平台出现下面错误,请运行位于web平台中的 Gradle 自定义任务

tasks.named<ProcessResources>("wasmJsProcessResources"){valrootBuild=rootProject.layout.buildDirectoryvalsqlitenowPath="wasm/node_modules/sqlitenow-kmp-library-core"from(rootBuild.file("$sqlitenowPath/sqlitenow-sqljs.js"),rootBuild.file("$sqlitenowPath/sql-wasm.wasm"),rootBuild.file("$sqlitenowPath/sqlitenow-indexeddb.js"))duplicatesStrategy=DuplicatesStrategy.INCLUDE}

错误截图如下

http://www.cnnetsun.cn/news/2530127.html

相关文章:

  • 接入Taotoken后如何通过用量看板分析与优化AI功能调用模式
  • 11期_js逆向核心案例解析(sichuan某理财网)
  • GeoSeg:突破性混合Transformer架构实现高效遥感图像语义分割
  • FlashAttention 在昇腾NPU上的极致优化
  • CRM系统签到列表数据重复BUG排查与修复(SQL Server存储过程)
  • 摆脱论文困扰!2026年最火AI论文写作工具榜单,毕业论文免费写还合规
  • 法律大模型幻觉致败诉案例激增47%?资深刑辩律师手把手教你构建3重事实校验Agent
  • 专业级.NET条码识别与生成:ZXing.Net全面指南
  • 滴滴多篇论文入选 ICML2026,值得一读!
  • FastGithub终极指南:如何5分钟解决GitHub访问缓慢问题
  • 射频线/PCB微带线隔离机理与高衰减器屏蔽设计
  • 在Python中快速接入Taotoken实现多模型调用,告别单一模型依赖
  • 终极指南:如何在5分钟内快速部署Open WebUI开源AI平台
  • 利用Taotoken模型广场为你的智能客服场景选择最合适的大模型
  • 初创团队如何利用Taotoken统一API与多模型能力加速产品原型开发
  • DOM 性能与渲染
  • UE5库存系统设计:FStruct+GameplayTags数据驱动方案
  • 零基础30天掌握渗透测试实战路径
  • kswapd0异常飙升?Linux内核级挖矿攻击深度排查与清除
  • 【MySQL全面教学】MySQL基础SQL语句Day3(2026年)
  • Hurley开源工具:C#到C语言的语义级跨平台翻译
  • JustTrustMe与Frida协同构建Android可信动态分析基座
  • 大模型MoE架构揭秘:为何仅2%参数决定推理性能
  • 企业团队如何利用Taotoken统一管理多项目API密钥与用量
  • DownKyi终极指南:5个技巧让你成为B站视频下载专家
  • Unity Shader从GPU原理入门:顶点与片元着色器硬核解析
  • 观察在流量高峰时段通过Taotoken调用不同模型的响应时间表现
  • Win11Debloat:三步让你的Windows 11告别卡顿,重获新生
  • 【YOLO目标检测全栈实战】69 内存碎片化:量化模型在边缘设备上的隐形杀手
  • Unity手搓合并网格工具:从Draw Call优化到生产级鲁棒性