当前位置: 首页 > 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.jsqmd.com/news/871991/

相关文章:

  • 2026年东莞GEO服务商可信赖排行榜TOP5推荐 - 速递信息
  • 数学论文降AI工具免费推荐:2026年数学毕业论文降AI4.8元知网达标免费完整方案
  • TabNet: Attentive Interpretable Tabular Learning——一种具有可解释性的注意力表格学习模型
  • 航空航天论文降AI工具免费推荐:2026年航空航天毕业论文降AI知网4.8元免费99.26%完整方案 - 还在做实验的师兄
  • Jenkins 构建失败排查记录:mvn -U 把新版依赖被远程旧版覆盖
  • 2026年贵阳室内装修设计全案深度横评:从设计落地到智能家居的品质突围指南 - 优质企业观察收录
  • GDScriptDecomp:让Godot游戏逆向工程变得触手可及
  • ESP-IDF+vscode开发ESP32第十三讲——NVS
  • 华硕笔记本G-Helper显示管理全攻略:从色彩异常到专业校准的5步解决方案
  • 2026 出手闲置名表,西安添价收手表回收安全交易口碑良好 - 薛定谔的梨花猫
  • 使用Taotoken聚合API后项目月度Token消耗与延迟体感观测
  • 论文被吐槽逻辑乱?师姐安利这几个AI写作辅助网站
  • 2026 天津学历提升机构实测排行榜:成考 / 自考避坑指南,这 5 家才是真靠谱 - 商业科技观察
  • MDX-M3-Viewer终极指南:在浏览器中轻松查看魔兽争霸和星际争霸3D模型
  • 2026年贵阳中高端室内装修全案设计深度横评:从设计落地到智能交付的完整避坑指南 - 优质企业观察收录
  • [具身智能-856]:大模型,本质是就是一个执行自然语言的CPU,AI智能体就是组织自然语言让改“CPU”执行
  • 浅谈-机器人运动规划算法-在各类Robot中的落地应用
  • 2026年最新10款一人创业AI开发工具测评榜单
  • 行政管理论文降AI工具免费推荐:2026年行政管理毕业论文AIGC超标4.8元一次过知网完整指南 - 还在做实验的师兄
  • K 语言矩阵乘法代码简化攻略:从复杂到简洁,编程体验大提升!
  • BilibiliDown:简单三步掌握B站视频下载的终极指南
  • 多模态认知系统架构与跨模态特征对齐技术解析
  • 郑州驾培行业标杆实力评测:正通驾培集团深度解析 - 速递信息
  • 专业级.NET条码识别与生成:ZXing.Net全面指南
  • 2026年贵阳室内装修设计全案方案深度横评:从毛坯到精装的完整避坑指南 - 优质企业观察收录
  • 为什么技术写作需要Markdown Here:告别邮件格式噩梦的终极解决方案
  • 戴森球计划工厂蓝图架构深度解析:构建高效星际生产线的核心策略
  • 【Java并发编程】JMM Java内存模型:原子性、可见性、有序性、happens-before原则(附《思维导图》+《面试高频考点清单》)
  • 风味溯源与消费测评:2026年5月厦门正宗沙茶面权威排名及探店指南 - damaigeo
  • 11期_js逆向核心案例解析(sichuan某理财网)