【HarmonyOS学习笔记】2026-07-13 | 保存应用数据-关系型数据库Day 3date: 2026-07-13tags: [HarmonyOS, 关系型数据库, relationalStore, SQLite, StoreConfig, securityLevel, static, readonly, const, export, catch, BusinessError异步]1️⃣ 学习内容概览开始学习数据存储章节的关系型数据库部分。核心内容包括relationalStore 本地数据库认知本地 SQLite vs 服务器数据库、StoreConfig 配置name 文件名、securityLevel 安全等级 S1-S4、模块导出方式选择导出类 vs 导出实例的判断依据——有无实例状态、static readonly 内存机制static 属于类不属于实例、不会因实例化产生多份、常量组织方式class static readonly vs const 顶层常量的命名空间优势、catch 错误处理ArkTS 严格模式下 catch 参数不能直接标注类型、异步机制全景三种异步模式对照、异步/同步判断规则、异步因果关系、主动异步手段、Python async 误导辨析、教材源码与推论实现的异步调用差异fire-and-forget 时序隐患待验证。2️⃣ 核心知识点与实操问题一、关系型数据库本地存储 vs 服务器存储HarmonyOS 的relationalStore是本地 SQLite 数据库存在手机沙箱内不在服务器上。本地数据库relationalStore服务器数据库数据在哪手机文件系统沙箱内远程服务器底层实现SQLite轻量嵌入式MySQL/PostgreSQL 等需要网络吗不需要需要卸载应用数据没了数据还在换手机数据不跟着走登录可恢复与 Preferences 的对比PreferencesrelationalStore类型键值对关系型表/行/列适用简单配置字体大小、开关结构化数据多表关联、SQL 查询共同点都是本地文件、沙箱内、卸载即丢同二、StoreConfig 配置详解constSTORE_CONFIG:relationalStore.StoreConfig{name:Objective.db,securityLevel:relationalStore.SecurityLevel.S1};name数据库文件名。系统在沙箱中自动创建文件开发者不需要指定路径和 Preferences 的 name 类似。.db是 SQLite 常见后缀。预期沙箱路径在/data/storage/el2/database/下待实测验证。securityLevel安全等级关系型数据库特有Preferences 没有等级常量含义适用场景S1SecurityLevel.S1公开数据偏好设置、字体大小S2SecurityLevel.S2内部数据系统配置、非敏感个人信息S3SecurityLevel.S3敏感数据联系人、短信、健康数据S4SecurityLevel.S4核心凭据密码、密钥、认证三、导出类 vs 导出实例的选择依据核心判断有没有实例状态this.xxx。// 有状态 → 导出实例classPreferencesUtil{preference?:preferences.Preferences;// 实例属性getFontPreferences(context:Context){...}// 操作 this.preference}exportdefaultnewPreferencesUtil();// 纯常量 → 导出类classCommonConstants{staticreadonlyCREATE_SPORTS_TABLE_SQL:string...;// 属于类不属于实例}exportdefaultclassCommonConstants{...}导出实例导出类适用有状态的工具类纯常量集合调用实例.方法()类.常量类比“调工具的方法”“查常量表取值”四、static readonly 的内存机制关键澄清static 成员属于类本身不属于实例。不需要也不应该实例化。// ✅ 正确——直接用类名访问CommonConstants.CREATE_SPORTS_TABLE_SQL// ❌ 错误——static 成员不会复制到实例上newCommonConstants().CREATE_SPORTS_TABLE_SQL// undefined内存情况static 成员只存在一份挂在类对象上模块缓存保证所有 import 拿到同一个类对象不存在每实例化一次存一份的问题五、class static readonly vs const 顶层常量两种方式都只存一份内存无差异。区别在于组织方式// 方式Aconst 顶层常量——扁平exportconstCREATE_SPORTS_TABLE_SQL:string...;import{CREATE_SPORTS_TABLE_SQL}from...;// 方式Bclass static readonly——命名空间exportdefaultclassCommonConstants{staticreadonlyCREATE_SPORTS_TABLE_SQL:string...;}importCommonConstantsfrom...→ CommonConstants.CREATE_SPORTS_TABLE_SQLconst 顶层常量class static readonly组织性扁平散落在顶层有命名空间归类清晰导入按名{ XXX }整体后点取X.xxx归类感弱强教材选 class 的理由命名空间——SQL 语句全在 CommonConstants 下代码更清晰。六、catch(error) vs catch(err: BusinessError)// 写法1先接住 unknown再转换ArkTS 标准写法catch(error){leterrerrorasBusinessError;hilog.error(DOMAIN,TAG,code${err.code}, message${err.message});}// 写法2直接标注类型ArkTS 严格模式下可能编译不过catch(err:BusinessError){hilog.error(DOMAIN,TAG,code${err.code}, message${err.message});}catch(error)catch(err: BusinessError)参数类型unknownBusinessError直接访问 .code❌ 不能✅ 能安全性更安全不假设类型有风险类型假设可能错ArkTS 兼容✅ 标准写法⚠️ 严格模式可能不过七、异步机制全景重点章节7.1 RDBStoreUtil 中的异步模式对照方法异步模式关键字/APIcreateObjectiveRDB回调函数(err, rdbStore) { ... }createSportTablePromise .then/.catch.then().catch()createPlanTablePromise .then/.catch.then().catch()initSportTablePromise .then/.catch.then().catch()queryAllPlans同步querySqlSyncqueryAllPlansDuration同步querySqlSyncinsertPlanasync/awaitasyncawaitconditionalPlansQuery混合async声明但内部同步asyncquerySqlSyncupdatePlanasync/awaitasyncawaitdeletePlanasync/awaitasyncawait三种异步模式 一种同步全在这一个文件里。7.2 三种异步模式详解回调函数最原始relationalStore.getRdbStore(context,STORE_CONFIG,(err,rdbStore){this.objectiveRDBrdbStore;if(err){return;}})你把函数作为参数传进去系统完成后回头调用你的函数。约定第一个参数是错误err第二个参数是结果——Node.js 时代的 “Error-first callback”。Promise .then/.catch进化版this.objectiveRDB?.execute(SQL).then((){/* 成功 */}).catch((err){/* 失败 */})execute()返回 Promise 对象.then()注册成功回调.catch()注册失败回调。解决了回调地狱多层嵌套问题。async/await最直观asyncinsertPlan(sportID:number,duration:number,status:boolean){try{awaitthis.objectiveRDB?.insert(PLANS,sportData,...);}catch(error){...}}写起来像同步代码实际还是异步。async标记函数自动返回 Promiseawait等待 Promise 完成再往下走错误用try/catch。是 Promise .then/.catch 的语法糖。三种模式的进化线回调函数嵌套深、难读→ Promise .then/.catch链式扁平→ async/await最直观7.3 如何判断 API 是异步还是同步判断规则只有一条返回类型是PromiseXXX就是异步直接返回XXX就是同步。这是从 SDK 的.d.ts类型定义中查到的铁证异步版同步版getRdbStore(): PromiseRdbStoregetRdbStoreSync(): RdbStoreexecute(sql): PromiseValueTypeexecuteSync(sql): ValueTypeinsert(table, values): PromisenumberinsertSync(table, values): numberbatchInsert(table, values): PromisenumberbatchInsertSync(table, values): numberupdate(values, predicates): PromisenumberupdateSync(values, predicates): numberdelete(predicates): PromisenumberdeleteSync(predicates): numberquerySql(sql): PromiseResultSetquerySqlSync(sql): ResultSet识别规律返回PromiseXXX→ 异步返回XXX→ 同步函数名带Sync→ 同步版本参数接受回调函数 → 异步回调风格7.4 异步的因果关系——异步在前语法在后核心认知异步是操作的本质I/O慢操作语法只是接结果的方式。模式因果方向Promise 模式操作异步 → API返回Promise → 你用.then/await接结果回调模式操作异步 → API接受回调 → 你写回调函数接结果两种模式因果关系完全相同——都是因为操作异步了所以你需要一种机制来接结果。不是写了.then()才变异步也不是写了回调才导致异步。异步在先语法在后。生活类比餐厅点餐操作异步 做菜需要时间回调 “做好了打我电话”Promise .then “给我取餐号好了叫号”async/await “我站这等但别人可以插队先办”三种都是等的方式做菜慢是原因等的方式是结果7.5 为什么这些操作必须异步磁盘I/O是慢操作同步执行会阻塞主线程导致界面卡顿。操作耗时原因如果同步会怎样创建数据库读/写磁盘文件界面卡住执行建表SQL写磁盘界面卡住批量插入数据多次写磁盘界面卡住插入/更新/删除写磁盘界面短暂卡顿为什么查询用了同步API 提供了同步和异步两个版本数据量小时同步等待极短可以接受。大数据量应换用异步版querySql()。7.6 主动异步I/O——如何让自定义操作异步执行问题如果我需要让一个自定义操作异步执行框架没有提供异步版本怎么办答案让操作异步只有两种手段没有第三种。手段原理你是被动还是主动用框架提供的异步APIOS级异步I/O单线程内实现被动使用框架已封装好把操作放到另一个线程TaskPool/Worker多线程工作线程执行主动调度任何操作都能异步化方法用 TaskPoolimport{taskpool}fromkit.ArkTS;ConcurrentfunctionmyCustomIO(filePath:string):string{letcontentfs.readTextSync(filePath);// 同步I/O但在工作线程上跑letprocessedcontent.toUpperCase();fs.writeTextSync(filePath,processed);returnprocessed;}asyncfunctiondoMyCustomIO(){constresultawaittaskpool.execute(myCustomIO,/data/.../myfile.txt);console.info(完成:${result});}new Promise 不是真异步// ❌ executor 在主线程同步执行readTextSync 照样卡住functionmyCustomIO():Promisestring{returnnewPromise((resolve){letcontentfs.readTextSync(/path/to/file);// 阻塞主线程resolve(content.toUpperCase());});}Promise 只改变了返回值的包装方式不改变执行线程。框架异步API vs 你的自定义异步——本质区别框架异步API操作系统层面提供了异步I/O能力单线程内就能实现不卡主线程你自定义异步本质是开一个新线程去执行同步代码从主线程角度看是异步7.7 Python async 模型的误导Python 的async def让人以为加了关键字就变异步了但它的异步模型是单线程协程——协作式让步不是并行。Python asyncHarmonyOS 异步I/OHarmonyOS TaskPool线程数1个线程1个线程主线程多个线程并行吗❌ 协程交替不并行❌ 等I/O时干别的✅ 真正并行适合I/O密集I/O密集CPU密集CPU计算能异步吗❌ 会卡死线程❌ 会卡死主线程✅ 放到工作线程正确理解Python 的 async/await 单线程协作调度HarmonyOS 的主动异步 多线程两者名称相同机制完全不同关键词不创造异步异步是操作方式决定的八、教材源码 vs 推论实现异步调用差异与fire-and-forget时序隐患待验证差异对照GoalPageBuilder 中queryAllPlans()和queryAllPlansDuration()报错Property then does not exist on type GoalItem[]。对比教材源码后发现调用方式不同位置教材源码我的实现差异aboutToAppear 中查询this.plansSet RDBStoreUtil.queryAllPlans();await RDBStoreUtil.queryAllPlans().then(...)我多了.then()refreshSelectOption 中this.durationSet RDBStoreUtil.queryAllPlansDuration();await RDBStoreUtil.queryAllPlansDuration().then(...)我多了.then()refreshSelectOption 声明refreshSelectOption() { ... }async refreshSelectOption() { ... }我多了async报错原因queryAllPlans()返回GoalItem[]具体值不是Promise。.then()是 Promise 的方法对具体值调用类型错误。这是 7.3 中返回XXX是同步不能用.then()规则的直接体现。推论同步方法应直接接收返回值。教材源码的做法与 RDBStoreUtil 的返回类型定义一致。aboutToAppear中的async声明多余。三个建表/初始化方法虽然内部调了异步API但自身没有return Promise是 fire-and-forget 模式返回void。queryAllPlans返回具体值。整个方法中没有任何awaitasync无意义。fire-and-forget 存在时序隐患。createSportTable()异步建表但不等完成就返回紧接着initSportTable()往可能还不存在的表里插数据再接着queryAllPlans()查询可能还没插完的表。三步应该是串行的建表→插数据→查询但 fire-and-forget 破坏了这个顺序保证。conditionalPlansQuery的.then()用法冗余。教材源码中await RDBStoreUtil.conditionalPlansQuery(...).then(...)——await和.then()同时使用是重复的直接this.plansSet await RDBStoreUtil.conditionalPlansQuery(...)即可。待验证验证项方法预期教材的实现fire-and-forget 同步查询在实机上是否正常按教材源码原样运行数据量小时可能正常异步操作可能比下一行同步代码先完成但逻辑上不可靠推论方案RDBStoreUtil 方法返回 Promise 调用方 await 串行是否正常改 RDBStoreUtil 三个方法为 asyncawait 返回 PromiseGoalPageBuilder 中 await 串行调用时序严格正确不会出现表还没建好就插数据的问题3️⃣ 为什么这些问题出现刨根问底 我的理解关系型数据库存本地而非服务器是移动端设计的常态手机应用需要离线可用不可能每次操作都请求服务器。SQLite 是嵌入式数据库的经典选择——轻量、零配置、单文件、整个数据库就是一个 .db 文件。HarmonyOS 把它封装成relationalStoreAPI开发者不需要直接操作 SQLite但底层就是它。securityLevel 是 HarmonyOS 的安全设计哲学移动设备上不同数据的敏感度差异极大——字体大小谁看都无所谓但密码泄露就是灾难。HarmonyOS 要求创建数据库时声明安全等级系统可以据此实施不同强度的保护加密方式、访问控制、备份策略等。这是安全左移的思路——在创建数据时就定义它的安全边界而不是事后补救。导出类 vs 导出实例的本质是有没有状态PreferencesUtil 有preference属性方法依赖this.preference所以必须实例化后导出。CommonConstants 全是静态常量没有实例属性导出类就够了。强行export default new CommonConstants()也不会出错但没意义——实例上没有东西可访问static 成员不在实例上。static 的属于类不属于实例是 TypeScript/ArkTS 的核心概念很多人以为new 一次就存一份这是把实例属性和静态成员搞混了。实例属性this.xxx确实每 new 一次存一份但 static 成员从一开始就只存在于类对象上跟实例无关。这个误解在学习中很常见。异步的本质是操作方式决定的不是语法创造的学了 Python 的 async/await 后容易误以为加了关键字就变异步了但 Python 的 async 是单线程协程调度协作式让步HarmonyOS 的主动异步是多线程真并行。两者的区别不是语法的区别而是线程模型的区别。在 HarmonyOS 中异步只有两个来源①操作系统的异步I/O框架封装好的API②把操作丢到另一个线程TaskPool/Worker。new Promise不是真异步它只是换了返回值的包装方式执行线程不变。框架对同一个操作提供同步和异步两种API是选择权交给开发者的设计querySql返回 Promise异步querySqlSync直接返回 ResultSet同步。框架不替你决定——数据量小时用同步代码更简单数据量大时用异步避免卡UI。这种设计在 HarmonyOS API 中很常见函数名带Sync 同步版不带 异步版。4️⃣ 实操验证记录#问题现象排查过程解决1关系型数据库存在本地还是服务器不确定 relationalStore 的数据位置对比本地数据库和服务器数据库特征本地 SQLite沙箱内不在服务器2StoreConfig 参数含义看到 name 和 securityLevel 不理解拆解每个字段name文件名securityLevelS1~S4安全等级3导出类 vs 导出实例的选择PreferencesUtil 导出实例CommonConstants 导出类规则不清对比两者结构差异有状态→导出实例纯常量→导出类4static readonly 内存占用担心导出类后每个模块实例化各存一份澄清 static 属于类不属于实例static 只存一份不需要实例化5const vs classstatic readonly两种都能定义常量不知如何选对比组织方式和引用方式内存无差异class 有命名空间优势6catch 参数类型标注不确定能否直接 catch(err: BusinessError)对比两种写法ArkTS 严格模式应先 catch unknown 再 as 转换7SQL 拼接空格缺失TEXT NOT NULLTEXTNOT NULLSQL 语法错误检查拼接处空格拼接处必须补空格或改用反引号模板字符串8哪些方法是异步的RDBStoreUtil 中三种异步模式混用逐方法分析代码回调/Promisethen/asyncawait三种模式查询用了同步9怎么判断API是异步还是同步不知道execute为什么是异步查SDK .d.ts类型定义返回PromiseXXX 异步返回XXX 同步带Sync 同步版10回调vs Promise的因果关系混淆了异步在前还是语法在前分析两种模式的因果链两者因果相同操作异步→需要接结果的语法异步在先语法在后11主动异步I/O怎么做框架没有异步版本的自定义操作如何异步执行对比框架异步API vs 换线程 vs new Promise两种手段框架异步API被动或 TaskPool/Worker换线程主动new Promise不是真异步12Python async模型的误导以为 async/await 就是加了关键字就变异步对比 Python 单线程协程 vs HarmonyOS 多线程Python协作调度单线程HarmonyOS多线程真并行关键词不创造异步13教材源码vs推论实现的异步调用差异GoalPageBuilder对同步方法调.then()报错对比教材源码发现调用方式不同同步方法直接接收返回值fire-and-forget有时序隐患推论方案待实机验证5️⃣ 最终结论最佳实践维度建议relationalStore 定位本地 SQLite 数据库离线可用卸载即丢与 Preferences 互补——简单配置用 Preferences结构化数据用 relationalStoreStoreConfigname 决定文件名不需要路径securityLevel 声明安全等级S1 公开 → S4 核心导出方式选择有实例状态this.xxx→export default new纯常量集合 →export default classstatic 成员属于类不属于实例不需要实例化只存一份模块缓存保证全局唯一常量组织方式class static readonly 有命名空间优势X.xxx归类清晰const 顶层常量更扁平灵活按项目规模选择catch 错误处理ArkTS 标准写法catch(error) { let err error as BusinessError; }先接住 unknown 再按需转换SQL 拼接空格拼接不会自动加空格拼接处必须在上一行末尾或下一行开头加空格推荐用反引号模板字符串避免此问题异步判断返回PromiseXXX 异步返回XXX 同步函数名带Sync 同步版本查 SDK .d.ts 确认异步因果关系异步是操作的本质I/O慢操作语法只是接结果的方式不是写了.then才变异步而是API异步才用.then三种异步模式回调函数最原始→ Promise.then/.catch链式→ async/await最直观进化方向是可读性主动异步只有两个手段①用框架异步APIOS级异步I/O②TaskPool/Worker换线程new Promise不是真异步Python vs HarmonyOSPython async 单线程协程协作调度HarmonyOS主动异步 多线程真并行关键词不创造异步fire-and-forget隐患方法内部调异步API但不return Promise → 调用方无法await → 时序不可控建表还没完成就可能插数据// StoreConfig 配置constSTORE_CONFIG:relationalStore.StoreConfig{name:MyDatabase.db,securityLevel:relationalStore.SecurityLevel.S1};// 有状态 → 导出实例classPreferencesUtil{preference?:preferences.Preferences;getFontPreferences(context:Context){...}}exportdefaultnewPreferencesUtil();// 纯常量 → 导出类exportdefaultclassCommonConstants{staticreadonlyCREATE_TABLE_SQL:string...;}// 使用CommonConstants.CREATE_TABLE_SQL不需要 new// catch 标准写法catch(error){leterrerrorasBusinessError;hilog.error(DOMAIN,TAG,code${err.code}, message${err.message});}// SQL 拼接空格问题// ❌ 拼接处没空格TEXT NOT NULL → TEXTNOT NULL// ✅ 拼接处补空格TEXT NOT NULL → TEXT NOT NULL// ✅ 更推荐用反引号模板字符串不需要拼接staticreadonlyCREATE_TABLE_SQL:stringCREATE TABLE IF NOT EXISTS SPORTS ( ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, TYPE TEXT NOT NULL );// 异步判断看返回类型// execute(sql): PromiseValueType → 异步用 .then 或 async/await// querySqlSync(sql): ResultSet → 同步直接拿结果// 函数名带 Sync 同步版本// 三种异步模式// 1. 回调getRdbStore(context, config, (err, store) { ... })// 2. Promise .then/.catchexecute(SQL).then(() {}).catch(() {})// 3. async/awaitasync fn() { try { await insert(...) } catch(e) {} }// 主动异步用 TaskPool 把同步操作丢到工作线程// import { taskpool } from kit.ArkTS;// Concurrent function myOp() { 同步代码 }// const result await taskpool.execute(myOp, args);// ❌ new Promise 不是真异步——executor 在主线程同步执行// new Promise(resolve { fs.readTextSync(path) }) // 照样卡主线程核心原则关系型数据库是本地 SQLite与 Preferences 互补结构化数据 vs 键值对。模块导出方式取决于有没有实例状态——有状态导出实例纯常量导出类。static 成员属于类不属于实例不存在每实例化一次存一份的问题。异步的本质是操作方式决定的不是语法创造的——返回PromiseXXX的API是异步返回XXX是同步异步在先语法在后主动异步只有两种手段框架异步API TaskPool/Worker换线程new Promise不是真异步。Python 的 async 是单线程协程HarmonyOS 的主动异步是多线程两者名称相同机制完全不同。SQL 字符串拼接时不会自动加空格拼接处必须补空格或改用反引号模板字符串。学习小结今天开始学习关系型数据库首先确认了 relationalStore 是本地 SQLite 数据库而非服务器存储与 Preferences 互补结构化数据 vs 键值对。理解了 StoreConfig 的两个必填参数name文件名系统自动管理路径和 securityLevelS1-S4 安全等级。理清了导出类与导出实例的选择依据——有实例状态导出实例纯常量导出类。澄清了 static readonly 的内存误解——static 属于类不属于实例不会因实例化产生多份。对比了 classstatic readonly 与 const 顶层常量的组织差异命名空间 vs 扁平。确认了 ArkTS 中 catch 参数应先接 unknown 再 as 转换的标准写法。发现了 SQL 字符串拼接的空格陷阱。重点深入了异步机制三种异步模式回调→Promise.then→async/await是进化关系判断API异步/同步看返回类型Promise异步直接返回同步Sync同步版异步的因果关系是操作异步在先语法接结果在后两种模式因果方向相同主动异步只有两种手段框架异步API TaskPool/Worker换线程new Promise 不是真异步纠正了 Python async 模型的误导——Python 是单线程协程协作调度HarmonyOS 主动异步是多线程真并行关键词不创造异步。最后通过报错排查发现了异步知识的实战应用对同步方法返回具体值调.then()会类型报错fire-and-forget 模式内部异步但不return Promise存在时序隐患推论了串行 await 方案并待实机验证。懿路向前 · AI辅助整理2026-07-13