news 2026/9/1 17:37:04

【听见课堂 HarmonyOS NEXT 实战系列 14】HarmonyOS 数据库升级实战:从 Schema v1 迁移到 v2

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【听见课堂 HarmonyOS NEXT 实战系列 14】HarmonyOS 数据库升级实战:从 Schema v1 迁移到 v2

【听见课堂 HarmonyOS NEXT 实战系列 14】HarmonyOS 数据库升级实战:从 Schema v1 迁移到 v2

第一次安装时建表很容易,真正困难的是已经有用户数据后再改表。直接把CREATE TABLE增加两个字段,只对新数据库有效;旧设备上的表结构不会自动变化。如果应用读取不存在的列,轻则页面报错,重则用户无法进入应用。

听见课堂当前把 RelationalStore schema 提升到 v2:课程新增color_token,任务新增due_at_ms,并将旧任务的自然语言截止时间回填为时间戳。本文按照migrateAndSeed()migrateV1ToV2()的真实实现,拆解版本检测、事务、回填和测试矩阵。

一、为什么需要 v2

schema v1 已经能保存课程、字幕、板书和任务,但后续出现两个新需求。

1. 课程需要稳定的颜色语义

如果页面只根据列表位置分配颜色,课程排序变化后同一课程会变色。v2 在courses增加:

color_tokenTEXTNOTNULLDEFAULT'ocean_blue'

存的是主题 token,而不是固定色值,方便暗色和高对比模式在 UI 层映射。

2. 任务中心需要真实时间排序

v1 只有due_text,例如“今晚”“明天 20:00 前”“本周五前”。每次打开页面重新解析会让“明天”不断向后移动,也无法稳定判断是否过期。v2 增加:

due_at_msINTEGERNOTNULLDEFAULT0

迁移时以同一个基准时刻解析旧文本,之后排序、日期分组和过期判断都读取固定时间戳。

二、版本号必须是代码常量和数据库记录的组合

当前代码声明:

constSCHEMA_VERSION:number=2;constMETA_SCHEMA_VERSION:string='schema_version';

代码常量表示“当前应用能够理解的最高版本”,schema_meta中的值表示“这个数据库已经迁移到哪个版本”。启动时比较二者,才能决定首次建库、升级、正常打开还是拒绝降级读取。

只修改常量不写迁移,旧表不会变化;只改表不更新元数据,迁移可能每次启动重复执行。

三、migrateAndSeed()的完整执行顺序

当前初始化流程可以归纳为:

beginTransaction -> 创建 schema_meta -> 读取旧版本 -> 版本过新则拒绝 -> CREATE TABLE IF NOT EXISTS 当前结构 -> version == 1 时执行 v1 -> v2 -> 首次需要时写入种子 -> 写入 schema_version = 2 commit 任何异常 -> rollBack -> 初始化失败 -> 上层切换内存降级

对应的核心代码:

privateasyncmigrateAndSeed():Promise<void>{conststore:relationalStore.RdbStore=this.getStore();store.beginTransaction();try{awaitstore.executeSql(CREATE_SCHEMA_META_SQL);constversion:number=awaitthis.readSchemaVersion();if(version>SCHEMA_VERSION){thrownewError('Database schema is newer than this application.');}awaitstore.executeSql(CREATE_COURSES_SQL);awaitstore.executeSql(CREATE_TRANSCRIPT_SQL);awaitstore.executeSql(CREATE_SCANS_SQL);awaitstore.executeSql(CREATE_TASKS_SQL);if(version===1){awaitthis.migrateV1ToV2();}// seed 与 meta 写入store.commit();}catch(error){store.rollBack();thrownewError('Failed to migrate classroom relational store.');}}

版本号只在所有步骤成功后更新为 2,避免迁移做到一半却被标记为完成。

四、为什么先执行CREATE TABLE IF NOT EXISTS

对全新数据库,schema_meta中没有版本,读取结果为 0。当前CREATE_*SQL 已经包含 v2 字段,因此直接创建最新结构,不需要从 v1 绕一圈。

对 v1 数据库,表已经存在,CREATE TABLE IF NOT EXISTS不会改变旧表,然后由migrateV1ToV2()增加字段。

这形成两条路径:

数据库状态version动作
全新安装0直接创建 v2 表并写种子
已有 v11保留数据并执行 ALTER/回填
已有 v22跳过迁移,正常读取
来自未来版本>2拒绝打开,避免旧应用破坏新结构

五、v1 到 v2 的两个ALTER TABLE

迁移方法先增加字段:

awaitstore.executeSql(`ALTER TABLE courses ADD COLUMN color_token TEXT NOT NULL DEFAULT 'ocean_blue'`);awaitstore.executeSql(`ALTER TABLE tasks ADD COLUMN due_at_ms INTEGER NOT NULL DEFAULT 0`);

两个字段都提供NOT NULL DEFAULT,这样已有行会获得可读值。没有默认值时,为含数据的旧表新增非空列往往会失败。

color_token使用统一默认值可以保证页面立即可渲染;后续若要按旧课程特征分配不同 token,应另写确定性回填规则。

六、为什么旧任务必须回填due_at_ms

仅增加默认值 0 虽然能完成建表,但所有旧任务都会变成“未指定日期”,任务中心无法判断过期和本周分组。因此迁移读取旧 ID 与due_text

constresultSet=awaitstore.querySql(`SELECT id, due_text FROM tasks ORDER BY sort_order`);consttaskIds:Array<string>=[];constdueTexts:Array<string>=[];try{while(resultSet.goToNextRow()){taskIds.push(this.getText(resultSet,'id'));dueTexts.push(this.getText(resultSet,'due_text'));}}finally{resultSet.close();}

先把结果复制到普通数组并关闭 ResultSet,再逐条更新,资源边界更清晰。

七、所有旧任务必须共享同一个迁移时刻

迁移代码只调用一次Date.now()

constmigrationNowMillis:number=Date.now();for(letindex:number=0;index<taskIds.length;index++){awaitthis.updateById(TABLE_TASKS,taskIds[index],{due_at_ms:TaskDateResolver.resolveDueText(dueTexts[index],migrationNowMillis)});}

如果每条任务各取一次当前时间,迁移跨过午夜时,“今天”和“明天”可能落到不同基准日。统一基准时刻保证同批回填一致。

八、自然语言回填不是无损转换

TaskDateResolver当前支持“今晚/今天”“明天”“周一至周日”“已过期/昨天/上周”等有限表达,不支持任意中文日期。

因此:

  • 可识别文本得到本地时间戳;
  • 不可识别文本返回 0;
  • “本周五”在不同迁移日期可能落在过去或未来;
  • 没有年份、时区和原始创建时间时,语义无法完全还原。

这不是迁移代码可以凭空解决的问题。更完善的 v1 设计应同时保存任务创建时间或原始解析基准;当前文章必须把due_at_ms=0视为有效降级,而不是伪造日期。

九、为什么要拒绝“数据库版本过新”

用户可能先安装新版产生 schema v3,之后回退到只支持 v2 的旧应用。旧代码不了解新字段、新约束和新状态,继续写入可能破坏数据。

当前保护是:

if(version>SCHEMA_VERSION){thrownewError('Database schema is newer than this application.');}

异常向上传递后,应用切到可见的内存降级。更理想的页面提示是“当前版本无法读取已有数据,请升级应用”,而不是自动删库。

十、事务能保护什么,不能保护什么

事务目标是让建表、ALTER、回填、种子和版本更新作为一个整体提交。失败时执行rollBack(),上层不应继续把数据库当作 v2 使用。

但工程上仍需在目标 HarmonyOS 版本验证 DDL 在事务中的真实回滚行为,不能只根据代码结构假设所有设备完全一致。尤其要测试:

  • 第一个 ALTER 成功、第二个失败;
  • ALTER 成功、回填中断;
  • 回填成功、写 meta 前进程结束;
  • 再次启动是否能安全恢复。

如果目标环境对部分 DDL 回滚有限制,就需要增加“列是否存在”探测或分阶段迁移状态,避免重复ADD COLUMN

十一、四类数据库状态必须分开测试

1. 首次安装

没有数据库文件和 meta。应直接得到 v2 表、默认课程颜色、任务时间戳和一次性种子。

2. 真实 v1 升级

先用 v1 schema 创建数据库并写入自定义课程、字幕、板书和任务,再安装 v2。验证原记录数量、正文和确认状态不变,新字段完成回填。

3. 空库

表存在但没有业务数据。迁移不能因为查询结果为空而失败,也不能在用户明确清空后每次启动重新注入种子。

4. 脏数据

至少覆盖空截止文本、无法解析日期、重复 ID、缺少 meta、版本过新和部分字段异常。结果可以降级或阻断,但不能静默删除用户内容。

十二、迁移后的验证不能只查版本号

schema_version=2只是一个信号。迁移完成后还应检查:

  • courses.color_tokentasks.due_at_ms列存在;
  • 旧课程、字幕、扫描和任务行数保持;
  • 已确认/已完成状态未改变;
  • 可识别的截止文本得到合理时间戳;
  • 不可识别文本保持due_at_ms=0并显示“未指定”;
  • P09 的排序、过期和日历分组符合预期;
  • 删除课程和清空全部数据事务仍有效;
  • 强停重启后仍读取 v2,不重复迁移。

可以把验证拆成结构、数据、业务三层,而不是只执行一条SELECT schema_version

十三、上下文文档与源码版本漂移怎么处理

项目早期架构文档可能仍写“当前 schema version 为 1”,而当前源码常量已经是 2。写技术文章或交付报告时,应明确采用当前源码和本轮验证结果,历史文档只作为当时基线。

正确做法是同步更新上下文或记录漂移,不应为了让材料一致而把源码事实写回 v1。可审计项目允许历史存在,但必须标注时间和适用版本。

十四、后续 v3 应怎样扩展

当 v3 到来时,不建议继续堆一个大函数。可以按版本逐级迁移:

letversion:number=awaitreadSchemaVersion();if(version<2){awaitmigrateV1ToV2();version=2;}if(version<3){awaitmigrateV2ToV3();version=3;}

每一步只理解相邻版本,配套独立夹具和后置条件。版本更新仍要在该步成功后发生。若数据量增大,还要考虑批量更新、进度提示、超时和可恢复中断。

十五、迁移验收清单

  • 当前 schema 常量与目标结构一致;
  • 全新安装直接创建最新结构;
  • v1 数据库通过 ALTER 和回填升级;
  • 新增非空列有合理默认值;
  • 所有旧任务共享同一迁移基准时刻;
  • ResultSet 在finally中关闭;
  • 版本过新时拒绝写入而不是删库;
  • 迁移失败切换为可见降级;
  • 首装、升级、空库、脏数据分别测试;
  • 验证字段、数据和页面业务,而不只检查版本号;
  • 没有把“代码存在迁移”写成“目标设备迁移已验证”。

十六、总结

数据库升级的本质是保护已有用户事实。听见课堂的 v1→v2 迁移在一个初始化事务中完成版本检测、字段新增、截止时间回填、种子控制和版本写入,并对未来版本设置拒绝保护。

这套实现已经具备清晰主链,但仍需要用真实 v1 数据库夹具验证 DDL 中断、脏数据和设备差异。只有迁移脚本存在、目标环境执行通过、迁移后业务页面正确三者同时成立,才能把数据库升级标记为passed

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 17:28:54

协同过滤算法本科毕业设计选题

300 个协同过滤算法本科毕业设计选题 选题分为 6 大类&#xff1a;传统协同过滤改进、混合推荐&#xff08;协同过滤 其他算法&#xff09;、数据稀疏 / 冷启动优化、场景化应用、相似度与权重优化、评测与对比研究&#xff0c;适合计算机、软件工程、大数据、人工智能本科毕设…

作者头像 李华
网站建设 2026/9/1 17:23:23

实体书管理软件:从扫码录入到多端同步的完整指南

这次我们来看一个实体书藏书管理软件&#xff0c;它同时提供了 App 和桌面端。对于喜欢买书、藏书的朋友来说&#xff0c;纸质书越来越多&#xff0c;管理就成了大问题&#xff1a;这本书我到底有没有&#xff1f;它放在书架第几层&#xff1f;借给谁了&#xff1f;这个项目就是…

作者头像 李华
网站建设 2026/9/1 17:22:37

EKF扩展卡尔曼滤波Matlab工程实现:从原理到调参实战

简介&#xff1a;本资源是一套面向控制工程、信号处理及导航定位方向本科生与研究生的扩展卡尔曼滤波&#xff08;EKF&#xff09;实践教学材料&#xff0c;聚焦非线性系统状态估计这一核心问题&#xff0c;适用于课程设计、仿真实验与算法入门学习。压缩包共17个文件&#xff…

作者头像 李华
网站建设 2026/9/1 17:22:29

数据中心关键设施解析:UPS容量计算与液冷散热实践

抱歉&#xff0c;这个主题涉及美国环境监管政策、政府机构及政治人物相关内容&#xff0c;属于政策与政治评论范畴&#xff0c;超出了我能够处理的技术内容范围。我无法将此类议题改写成技术博客。如果你有数据中心技术相关的内容&#xff0c;比如 UPS 电池容量计算、机房建设清…

作者头像 李华
网站建设 2026/9/1 17:22:00

同一份 KB 在桌面与 WPS 之间共用

chayuan-wps 加载项跟 察元AI智能体 共用同一套 KB。这一篇讲。 共用的核心 KB 数据存在 察元AI智能体 后端&#xff08;127.0.0.1:62581&#xff09;。 桌面 察元AI智能体 UI 用这些 KB。 WPS 加载项 chayuan-wps 也用这些 KB。 无需重复存储 / 同步。 实现机制 察元AI智能体 …

作者头像 李华
网站建设 2026/9/1 17:21:48

LPC1768 IAR工程解析:RAM.icf链接脚本与HardFault调试实战

简介&#xff1a;本资源是面向嵌入式初学者与LPC1768开发者的IAR平台基础工程包&#xff0c;专为快速上手NXP Cortex-M3架构单片机而设计&#xff0c;解决开发环境搭建、外设驱动验证及内存配置等入门痛点。压缩包共567个文件&#xff0c;含135个C源码&#xff08;如adc.c、uar…

作者头像 李华