Kotlin 跨平台 SqliteNow 全平台数据持久化方案
Kotlin 跨平台 SqliteNow 全平台数据持久化方案1. 环境与依赖配置1.0 创建一个Kotlin 多平台项目1.1 版本声明libs.versions.toml1.2 项目级插件配置build.gradle.kts1.3 模块级依赖配置app/shared/build.gradle.kts1.4 Web 平台配置Webpack1.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 MultiplatformKMP的全平台 SQLite 持久化库它通过编译时 SQL 验证、类型安全的查询构建器以及跨平台Android、iOS、Desktop、Web的统一 API为 KMP 项目提供了高效、可靠的数据存储解决方案。本文将以一个学生信息管理为例详细介绍如何从零开始集成 SqliteNow完成表结构定义、数据操作并在 JVMDesktop与 WebJS/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/shared的build.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.jsconfig.resolve{fallback:{fs:false,path:false,crypto:false,}};config.moduleconfig.module||{};config.module.rulesArray.isArray(config.module.rules)?config.module.rules:[];config.module.rules.push({test:/sql-wasm\.wasm$/,type:asset/resource,});constCopyWebpackPluginrequire(copy-webpack-plugin);config.plugins.push(newCopyWebpackPlugin({patterns:[../../node_modules/sql.js/dist/sql-wasm.wasm]}));1.5 WasmJs 平台资源复制配置对于 WebAssemblyWasmJs目标需在共享模块的构建脚本中添加资源复制任务确保 WASM 文件能被正确打包。在app/shared/build.gradle.kts的kotlin块后添加kotlin{sourceSets{webMain.dependencies{implementation(devNpm(copy-webpack-plugin,11.0.0))implementation(npm(sql.js,1.13.0))}}}tasks.namedProcessResources(wasmJsProcessResources){valrootBuildDirrootProject.layout.buildDirectoryvalsqlitenowPathwasm/node_modules/sqlitenow-kmp-library-corevalmoveFileListlistOf(sql-wasm.wasm,sqlitenow-sqljs.js,sqlitenow-indexeddb.js)moveFileList.forEach{fileName-from(rootBuildDir.file($sqlitenowPath/$fileName))}duplicatesStrategyDuplicatesStrategy.INCLUDE}构建完成后检查app/shared/build/processedResources/wasmJs/main中是否包含以下三个文件sql-wasm.wasm,sqlitenow-sqljs.js,sqlitenow-indexeddb.js2 创建数据库2.0 添加数据库构建脚本在app/shared模块级别build.gradle.kts的中添加数据库构建脚本sqliteNow{databases{create(AppDatabase){packageName.set(com.example.sqliteNow)debugfalse}}}2.1 添加学生的Kotlin类在app/shared/commonMain中创建sql目录, 然后在该目录下创建AppDatabase目录与构建脚本中名称相同最后在AppDatabase目录下, 创建schema和queries目录在app/shared/commonMain中创建Student.kt文件, 表示数据库中的学生实例packageorg.example.kmpsqlitenowdemoimportkotlinx.datetime.LocalDateimportkotlinx.serialization.SerializableSerializableenumclassGender{MALE,FEMALE}dataclassStudent(valid:Long0,valname:String,valage:Int,valgender:Gender,valbirthDate:LocalDate)2.2 添加学生表和基础查询语句在schema目录中, 创建一个学生表student.sqlCREATETABLEIFNOTEXISTSstudent(idINTEGERPRIMARYKEYNOTNULL,nameTEXTNOTNULL,ageINTEGERNOTNULL,-- {fieldgender, propertyTypeorg.example.kmpsqlitenowdemo.Gender, adaptercustom}genderTEXTNOTNULL,-- {fieldbirthDate, propertyTypekotlinx.datetime.LocalDate, adaptercustom}birthDateTEXTNOTNULL);在queries目录中创建student目录然后创建一个查询全部语句selectAll.sql-- { queryResultStudentEntity }SELECT*FROMstudent;在queries/student目录中创建一个添加学生的语句add.sqlINSERTINTOstudent(name,age,gender,birthDate)VALUES(:name,:age,:gender,:birthDate);最后同步项目运行 Gradle 任务生成数据库代码./gradlew :app:shared:generateAppDatabase2.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(){valdbAppDatabase(dbNameresolveDatabasePath(dbNamedatabase.db,appNameKmpSqliteNowDemo),migrationVersionBasedDatabaseMigrations(),studentAdaptersAppDatabase.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)valstudentListdb.student.selectAll.asList()println([Database] studentList:$studentList)if(studentList.find{it.nameGeorge}null){valparamsStudentQuery.Add.Params(nameGeorge,age19,genderGender.MALE,birthDateLocalDate(year2003,month7,day27))db.student.add(params)println([Database] add George successful)}valnowStudentListdb.student.selectAll.asList()println([Database] nowStudentList:$nowStudentList)delay(1000.milliseconds)db.close()println([Database] closed)}在app/shared/commonMain中App.kt文件的App函数下, 添加下面代码, 用于测试数据库funApp(){LaunchedEffect(Unit){testDatabase()}// ...}3. 测试数据库3.0 在JVM平台测试数据库然后先在desktop平台运行一下, 会产生以下输出[Database] opened [Database] studentList: [] [Database] add George successful [Database] nowStudentList: [StudentEntity(id1, nameGeorge, age19, genderMALE, birthDate2003-07-27)] [Database] closed重新运行输出结果如下, 说明学生George成功添加到了数据库中, 完成了持久化[Database] opened [Database] studentList: [StudentEntity(id1, nameGeorge, age19, genderMALE, birthDate2003-07-27)] [Database] nowStudentList: [StudentEntity(id1, nameGeorge, age19, genderMALE, birthDate2003-07-27)] [Database] closed3.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(id1, nameGeorge, age19, genderMALE, birthDate2003-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(id1, nameGeorge, age19, genderMALE, birthDate2003-07-27)] [Database] nowStudentList: [StudentEntity(id1, nameGeorge, age19, genderMALE, birthDate2003-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(id1, nameGeorge, age19, genderMALE, birthDate2003-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 bytes8192 [Database] opened [Database] studentList: [StudentEntity(id1, nameGeorge, age19, genderMALE, birthDate2003-07-27)] [Database] nowStudentList: [StudentEntity(id1, nameGeorge, age19, genderMALE, birthDate2003-07-27)] [Database] closed说明学生George成功添加到了数据库中, 完成了持久化3.3 Web平台常见错误修复如果在web平台中出现打开数据库后找不到student表的错误或者表格字段错误等等请在浏览器设置中清除浏览数据在尝试这将清除你在开发环境浏览器中保留的IndexedDB如果Web Wasm平台出现下面错误请运行位于web平台中的 Gradle 自定义任务tasks.namedProcessResources(wasmJsProcessResources){valrootBuildrootProject.layout.buildDirectoryvalsqlitenowPathwasm/node_modules/sqlitenow-kmp-library-corefrom(rootBuild.file($sqlitenowPath/sqlitenow-sqljs.js),rootBuild.file($sqlitenowPath/sql-wasm.wasm),rootBuild.file($sqlitenowPath/sqlitenow-indexeddb.js))duplicatesStrategyDuplicatesStrategy.INCLUDE}错误截图如下