【鸿蒙心迹】HarmonyOS笔记27:关系型数据库保存业务数据
🔥 个人主页: 杨利杰YJlio
❄️ 个人专栏: 《Windows 疑难杂症与工单复盘案例库》 《Sysinternals实战教程》
《WINDOWS教程》 《Windows PowerShell 实战》 《IOS插件分析测试》
🌟 让复杂的事情更简单,让重复的工作自动化


文章目录
大家好,我是杨利杰 YJlio。
前两节分别整理了设置与文件。这一节我用最小工单表练习业务记录的保存:先准备表,再插入一条记录,查询、更新,最后按指定ID删除练习记录。
本篇编号027,计划日期2026年10月17日。原稿已保留。原稿loadTickets只是返回空数组的占位,下面补充实际接口调用的学习代码;尚未在本机编译或设备运行,配图均为概念示意。
一、先把业务字段定下来
原稿的Ticket有id、title、status三个字符串字段。我保留这组结构,并为数据库列确定名称:
interface Ticket {
id: string;
title: string;
status: string;
}
| 业务字段 | 数据库列 | 本例类型与约束 |
|---|---|---|
| id | ID | TEXT,非空主键 |
| title | TITLE | TEXT,非空 |
| status | STATUS | TEXT,非空 |
T-001、打印机离线、new都是虚构练习值,不对应真实工单。status继续使用原稿的string,完整业务应用还需规定允许值与流转规则。

二、先取得RdbStore,再调用数据接口
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
async function openTicketStore(
context: common.UIAbilityContext
): Promise<relationalStore.RdbStore> {
const config: relationalStore.StoreConfig = {
name: 'TicketLearn.db',
securityLevel: relationalStore.SecurityLevel.S3
};
return await relationalStore.getRdbStore(context, config);
}
context应来自当前可用的UIAbility或页面宿主。TicketLearn.db是本例单独使用的学习库名称,不指向其他业务数据库。
securityLevel是存储配置的一部分,不等于已经启用了数据库加密;涉及真实数据时还需按业务敏感程度与对应SDK核对配置。本例不加入任何实际个人信息。

三、首次建表与后续升级分开
async function prepareTicketTable(store: relationalStore.RdbStore): Promise<void> {
await store.executeSql(
'CREATE TABLE IF NOT EXISTS TICKET (' +
'ID TEXT PRIMARY KEY NOT NULL, ' +
'TITLE TEXT NOT NULL, ' +
'STATUS TEXT NOT NULL)'
);
}
getRdbStore完成后,还要等待prepareTicketTable完成,再执行后面的增删查改。
IF NOT EXISTS用于避免表已存在时重复创建,不会自动把旧表更新为新的字段结构。这篇只提供第一次学习建表,不实现完整版本迁移;已有数据的工程应另做升级与回滚设计。
能打开数据库,不代表目标表已经准备好;建表也不等于完成结构迁移。

四、用ValuesBucket新增一条记录
async function addTicket(
store: relationalStore.RdbStore,
ticket: Ticket
): Promise<number> {
if (ticket.id.trim().length === 0 || ticket.title.trim().length === 0) {
throw new Error('工单ID与标题不能为空');
}
const values: relationalStore.ValuesBucket = {
ID: ticket.id,
TITLE: ticket.title,
STATUS: ticket.status
};
const rowId = await store.insert('TICKET', values);
if (rowId < 0) {
throw new Error('插入工单失败');
}
return rowId;
}
调用示例使用{id: ‘T-001’, title: ‘打印机离线’, status: ‘new’}。参数通过ValuesBucket交给接口,不拼接进SQL语句。
insert返回的是数值行号,本例业务ID仍是字符串T-001。这两者用途不同,不能把返回的number直接塞进Ticket.id。
重复插入相同主键可能失败。本例没有采用覆盖冲突策略,也不会为了重试而清空整个数据库。

五、查询转成数组,finally关闭结果集
async function loadTickets(store: relationalStore.RdbStore): Promise<Ticket[]> {
const predicates = new relationalStore.RdbPredicates('TICKET');
const result = await store.query(predicates, ['ID', 'TITLE', 'STATUS']);
const tickets: Ticket[] = [];
try {
const idIndex = result.getColumnIndex('ID');
const titleIndex = result.getColumnIndex('TITLE');
const statusIndex = result.getColumnIndex('STATUS');
while (result.goToNextRow()) {
tickets.push({
id: result.getString(idIndex),
title: result.getString(titleIndex),
status: result.getString(statusIndex)
});
}
return tickets;
} finally {
result.close();
}
}
查询成功且没有记录时,这个函数才返回空数组。query失败或字段读取失败应交给调用方处理,不能统一catch后return [],否则界面会把故障显示成“没有工单”。
本例只面向少量学习记录,没有分页、排序和完整列表界面。记录较多时应限制查询范围,不把一次性全表读取扩展为生产方案。

六、更新和删除都带业务ID条件
async function changeTicketStatus(
store: relationalStore.RdbStore,
id: string,
status: string
): Promise<number> {
if (id.trim().length === 0) {
throw new Error('工单ID不能为空');
}
const predicates = new relationalStore.RdbPredicates('TICKET');
predicates.equalTo('ID', id);
const values: relationalStore.ValuesBucket = { STATUS: status };
return await store.update(values, predicates);
}
async function removeTicket(
store: relationalStore.RdbStore,
id: string
): Promise<number> {
if (id.trim().length === 0) {
throw new Error('工单ID不能为空');
}
const predicates = new relationalStore.RdbPredicates('TICKET');
predicates.equalTo('ID', id);
return await store.delete(predicates);
}
更新示例把T-001状态改为processing。删除函数只用于自己创建的学习记录;本文没有执行任何数据库删除操作。
两个接口的返回值表示影响行数。0不等于接口一定抛异常,可能只是没有找到该ID。对于本例单行主键操作,应核对是否影响1行,再决定界面反馈。
更新与删除条件要明确,返回的影响行数也要检查。

七、失败由调用方反馈,不自动清库
初始化、建表、插入与查询都可能失败。调用方捕获错误后应记录阶段和完整错误,不显示虚构的“保存成功”。例如只插入和查询的调用函数:
import { BusinessError } from '@kit.BasicServicesKit';
async function insertAndRead(context: common.UIAbilityContext): Promise<Ticket[]> {
try {
const store = await openTicketStore(context);
await prepareTicketTable(store);
await addTicket(store, { id: 'T-001', title: '打印机离线', status: 'new' });
return await loadTickets(store);
} catch (error) {
const err = error as BusinessError;
console.error(`insertAndRead failed: ${err.code}, ${err.message}`);
throw error;
}
}
这是一次性插入练习,反复调用可能遇到主键冲突。错误没有转为空列表,页面还需要自己的失败提示和忙碌状态。普通Error不一定带平台错误码,应同时查看message。
代码块属于同一组函数与类型声明;在实际文件中整理导入,不重复粘贴声明。本篇不实现完整页面、并发控制、版本迁移或多步骤事务服务;RdbStore实例的持有与释放要按实际使用生命周期安排,不能在其他调用仍使用时关闭。

八、沿增删查改路径核对结果
| 步骤 | 预期或需检查内容 | 当前状态 |
|---|---|---|
| 打开TicketLearn.db并建表 | 完成后再操作TICKET | 待验证 |
| 插入T-001 | 数值行号有效,业务ID仍为T-001 | 待验证 |
| 查询 | ID、TITLE、STATUS映射到Ticket | 待验证 |
| 更新状态为processing | 核对影响1行,再查询状态 | 待验证 |
| 正常重启后只查询 | 在删除前核对记录是否仍在 | 待验证 |
| 删除学习记录T-001 | 核对影响1行,再查询确认 | 待验证 |
| 重复插入或异常输入 | 捕获错误,不把失败当空列表 | 待验证 |
重启核验时不要再次调用insertAndRead,应打开同名库、准备表后只调用loadTickets。保留实际DevEco Studio、SDK/API、设备与完整日志。本文函数采用Promise形式,需与工程的接口重载匹配。
示例中的行号、影响行数和重启读回都是检查目标,实际结果仍需设备验证。

写在最后
这篇我把原稿的空数组占位补成了数据库操作路径:业务字段对应列,表准备好再写入,查询结果集及时关闭,更新和删除限定业务ID。
下一篇继续处理数据库记录的列表展示与更新,重点放在持久化结果如何回到页面状态。
参考资料
- OpenHarmony官方文档:关系型数据库持久化,用于核对基本流程。
- OpenHarmony官方文档:RdbStore接口,用于核对executeSql、insert、query、update、delete;实际HarmonyOS工程以对应SDK资料为准。
- 本系列第027篇原稿:《用关系型数据库保存业务数据》,计划日期2026-10-17。
声明:本文为个人学习笔记。数据为虚构演示值,代码和配图未经过SDK编译或设备实测,未操作真实业务数据库。

更多推荐



所有评论(0)