鸿蒙操作系统使用 relationalStore 实现本地 RDB 数据库增删改查实战
·
一、背景说明
HarmonyOS 7 正在向更多机型开放公测(nova 12/13、畅享 70X 等),应用生态对本地结构化数据存储的需求也随之增多。本文不谈系统版本本身,聚焦一个日常开发绕不开的技术点:使用 ArkData 的 @ohos.data.relationalStore(RDB Store)在鸿蒙应用内实现一个真正可用的本地关系型数据库,完成建表、增删改查、事务和结果集遍历的完整闭环。
二、技术原理
RDB Store 是鸿蒙基于 SQLite 封装的关系型数据库能力,核心概念如下:
- StoreConfig:数据库配置,
name是库文件名(同一进程内必须唯一),securityLevel是必填的安全等级(S1~S4),encrypt可选是否加密。 - RdbStore:通过
relationalStore.getRdbStore(context, config)获取的数据库实例,所有读写都经由它。 - RdbPredicates:谓词对象,链式拼接查询条件(
equalTo、between、orderByAsc等),等价于 SQL 的 WHERE/ORDER BY。 - ResultSet:查询结果集游标,逐行
goToNextRow()读取。 - 数据流:
UI 组件 → 数据管理类 DbManager → RdbStore → SQLite 文件(应用沙箱 databaseDir/rdb/ 下)。
三、实现步骤
工程为 Stage 模型 ArkTS 应用,无需任何额外权限(数据库在应用沙箱内),module.json5 保持默认即可。
1. 数据库管理类 DbManager.ets
import { relationalStore } from '@kit.ArkData';
import { Context } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
const TABLE_NAME = 'todo';
const CREATE_TABLE_SQL = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
level INTEGER DEFAULT 1,
done INTEGER DEFAULT 0,
create_time TEXT
)`;
export interface TodoItem {
id: number;
title: string;
level: number;
done: boolean;
createTime: string;
}
export class DbManager {
private rdbStore: relationalStore.RdbStore | null = null;
// 初始化数据库并建表
async init(context: Context): Promise<void> {
const config: relationalStore.StoreConfig = {
name: 'todo.db',
securityLevel: relationalStore.SecurityLevel.S1,
encrypt: false
};
this.rdbStore = await relationalStore.getRdbStore(context, config);
await this.rdbStore.execute(CREATE_TABLE_SQL);
}
// 新增一条记录,返回行 id
async insertTodo(title: string, level: number): Promise<number> {
const bucket: relationalStore.ValuesBucket = {
'title': title,
'level': level,
'done': 0,
'create_time': new Date().toISOString()
};
return await this.rdbStore!.insert(TABLE_NAME, bucket);
}
// 查询全部,按 level 降序、创建时间倒序
async queryAll(): Promise<TodoItem[]> {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.orderByDesc('level').orderByDesc('create_time');
const resultSet = await this.rdbStore!.query(predicates, ['id', 'title', 'level', 'done', 'create_time']);
const list: TodoItem[] = [];
try {
while (resultSet.goToNextRow()) {
list.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
title: resultSet.getString(resultSet.getColumnIndex('title')),
level: resultSet.getLong(resultSet.getColumnIndex('level')),
done: resultSet.getLong(resultSet.getColumnIndex('done')) === 1,
createTime: resultSet.getString(resultSet.getColumnIndex('create_time'))
});
}
} finally {
resultSet.close(); // 结果集必须手动关闭,否则泄漏句柄
}
return list;
}
// 更新完成状态
async updateDone(id: number, done: boolean): Promise<number> {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('id', id);
const bucket: relationalStore.ValuesBucket = { 'done': done ? 1 : 0 };
return await this.rdbStore!.update(bucket, predicates);
}
// 删除已完成(done=1)的记录
async deleteFinished(): Promise<number> {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('done', 1);
return await this.rdbStore!.delete(predicates);
}
}
export const dbManager = new DbManager();
2. UI 调用示例 Index.ets
import { dbManager, TodoItem } from '../model/DbManager';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
@Entry
@Component
struct Index {
@State todos: TodoItem[] = [];
@State inputTitle: string = '';
private context = getContext(this) as common.UIAbilityContext;
async aboutToAppear(): Promise<void> {
try {
await dbManager.init(this.context);
this.todos = await dbManager.queryAll();
} catch (err) {
const e = err as BusinessError;
console.error(`db init failed, code=${e.code}, msg=${e.message}`);
}
}
async addTodo(): Promise<void> {
if (this.inputTitle.trim() === '') {
return;
}
try {
await dbManager.insertTodo(this.inputTitle.trim(), 1);
this.inputTitle = '';
this.todos = await dbManager.queryAll(); // 重新查询刷新列表
} catch (err) {
const e = err as BusinessError;
console.error(`insert failed, code=${e.code}`);
}
}
build() {
Column({ space: 12 }) {
Text('待办事项(RDB 持久化)')
.fontSize(20).fontWeight(FontWeight.Bold)
Row({ space: 8 }) {
TextInput({ text: this.inputTitle, placeholder: '输入待办内容' })
.layoutWeight(1)
.onChange((value: string) => { this.inputTitle = value; })
Button('添加').onClick(() => { this.addTodo(); })
}
List({ space: 8 }) {
ForEach(this.todos, (item: TodoItem) => {
ListItem() {
Row({ space: 8 }) {
Text(item.done ? '[已完成]' : '[进行中]')
.fontSize(14)
.fontColor(item.done ? '#888888' : '#007DFF')
Text(item.title).fontSize(16).layoutWeight(1)
Button(item.done ? '撤销' : '完成')
.fontSize(12)
.onClick(() => {
dbManager.updateDone(item.id, !item.done)
.then(async () => {
this.todos = await dbManager.queryAll();
});
})
}.width('100%').padding(8)
}
}, (item: TodoItem) => item.id.toString())
}.layoutWeight(1)
Button('清理已完成').width('100%')
.onClick(() => {
dbManager.deleteFinished()
.then(async (rows: number) => {
console.info(`deleted ${rows} rows`);
this.todos = await dbManager.queryAll();
});
})
}
.padding(16)
.width('100%')
.height('100%')
}
}
四、应用场景示例
切换应用或重启设备后数据仍在,验证持久化效果:
// 重启后打印库内全部记录
dbManager.init(this.context).then(async () => {
const list = await dbManager.queryAll();
list.forEach((item: TodoItem) => {
console.info(`id=${item.id}, title=${item.title}, done=${item.done}`);
});
});
1. 事务批量写入
插入 1000 条记录时逐条 insert 会明显慢,用 startTransaction 包裹可将耗时降低一个数量级:
async batchInsert(titles: string[]): Promise<void> {
await this.rdbStore!.startTransaction();
try {
for (const t of titles) {
const bucket: relationalStore.ValuesBucket = {
'title': t, 'level': 1, 'done': 0, 'create_time': new Date().toISOString()
};
await this.rdbStore!.insert(TABLE_NAME, bucket);
}
await this.rdbStore!.commit();
} catch (err) {
await this.rdbStore!.rollBack(); // 任一条失败整体回滚
throw err as BusinessError;
}
}
2. 安全等级与加密
securityLevel建议按数据敏感度选择:普通配置S1、含个人信息的S2起步;等级不匹配系统会拦截跨应用数据访问。- 涉及账号、令牌等敏感数据时将
encrypt设为true,库文件整体加密落盘。
3. 错误处理
常见错误码要针对性捕获:
try {
await dbManager.insertTodo('demo', 1);
} catch (err) {
const e = err as BusinessError;
if (e.code === 14800011) {
console.error('库文件不存在或已损坏,可删除后重建');
} else if (e.code === 14800014) {
console.error('结果集/句柄已关闭,检查 ResultSet 生命周期');
} else {
console.error(`db error code=${e.code}, msg=${e.message}`);
}
}
4. 使用限制
- 单条记录建议小于 2MB,超大字段改用 BLOB 或文件系统存储路径。
- 同一进程不要用相同
name创建两个库;ResultSet用完必须close()。 - 数据库操作耗时随数据量增长,大数据量查询建议放在 TaskPool 子线程,避免阻塞 UI。
六、扩展建议
在本文基础上可以继续演进:
- 云端同步:将
StoreConfig配合 distributedDB 能力,把同一张表在设备间同步; - 向量检索:API 18+ 支持
vector: true的向量库,可结合端侧 AI 做语义搜索; - 备份恢复:调用
backup()/restore()在升级迁移前做库文件快照:
await rdbStore.backup('todo_backup.db'); // 一行完成备份
结语
本文用 relationalStore 完成了鸿蒙应用本地数据库从建库建表到增删改查、事务批量写入的完整实现,核心代码约 150 行,全部可直接编译运行。掌握 RDB Store 后,应用内的列表缓存、离线数据、结构化配置都有了一个可靠且可扩展的落地方案。
更多推荐



所有评论(0)