当前位置:首页>鸿蒙APP>鸿蒙 ArkTS RelationalStore 实战全攻略:从零搭建企业级本地数据库方案

鸿蒙 ArkTS RelationalStore 实战全攻略:从零搭建企业级本地数据库方案

  • 2026-10-30 04:09:37
鸿蒙 ArkTS RelationalStore 实战全攻略:从零搭建企业级本地数据库方案

鸿蒙 ArkTS RelationalStore 实战全攻略:从零搭建企业级本地数据库方案

本文适合有一定 ArkTS 基础的开发者,覆盖 RelationalStore 的核心 API、Repository 封装、事务管理、数据迁移升级,以及常见坑位。代码基于 HarmonyOS SDK 5.0(API Level 12)。


为什么要用 RelationalStore?

鸿蒙提供了多种本地存储方案:

方案
适用场景
容量上限
Preferences
简单 Key-Value 配置
单文件 < 1 MB
KVDB
分布式键值存储
中等
RelationalStore(RDB)结构化数据,复杂查询GB 级
FileIO
文件/二进制数据
磁盘限制

对于用户笔记、订单、聊天记录等场景,RelationalStore 是唯一正确选择——它基于 SQLite,支持事务、联表查询、索引优化,且提供了全套 TypeScript 类型声明。


一、权限配置与初始化

1.1 module.json5 权限声明

{  "module": {    "requestPermissions": [      {        "name": "ohos.permission.READ_MEDIA",        "reason": "{tasks.length} 条成功`);  } catch (err) {    await store.rollBack(); // 回滚    console.error('[TaskRepository] 批量插入失败,已回滚', err);    throw err;  }}

最佳实践:任何涉及多条写操作(插入/更新/删除组合)的业务逻辑,都应该放在事务里。事务是数据一致性的最后防线。


四、数据库版本升级与数据迁移

应用迭代时常需要新增字段或新建表,错误的迁移方式会导致用户数据丢失。

// database/DbManager.ts(升级版)const DB_VERSION = 2; // 版本号递增const storeConfig: relationalStore.StoreConfig = {  name: DB_NAME,  securityLevel: relationalStore.SecurityLevel.S1,};// 迁移脚本:版本 n → 版本 n+1const MIGRATIONS: Record<number, string[]> = {  // v1 → v2:task 表新增 tags 字段,新建 category 表  1: [    `ALTER TABLE task ADD COLUMN tags TEXT DEFAULT ''`,    `CREATE TABLE IF NOT EXISTS category (      id   INTEGER PRIMARY KEY AUTOINCREMENT,      name TEXT NOT NULL UNIQUE    )`,  ],};export class DbManager {  // ...(省略单例部分)  async init(context: common.UIAbilityContext): Promise<void> {    if (this.rdbStore) return;    this.rdbStore = await relationalStore.getRdbStore(context, storeConfig);    // 读取当前版本    const currentVersion = await this.rdbStore.version;    if (currentVersion === 0) {      // 全新安装:直接执行最新建表语句      await this.rdbStore.executeSql(CREATE_TABLE_TASK);      await this.rdbStore.version = DB_VERSION;    } else if (currentVersion < DB_VERSION) {      // 逐步迁移      for (let v = currentVersion; v < DB_VERSION; v++) {        const sqls = MIGRATIONS[v] ?? [];        for (const sql of sqls) {          await this.rdbStore.executeSql(sql);          console.info(`[Migration] v{v + 1}: {Date.now()}`,      content: '点击编辑内容',      status: 0,      priority: 2,    });    await this.loadTasks();  }  async toggleStatus(id: number, currentStatus: number) {    const next = currentStatus === 2 ? 0 : currentStatus + 1;    await repo.update(id, { status: next });    await this.loadTasks();  }  async removeTask(id: number) {    await repo.delete(id);    await this.loadTasks();  }  build() {    Column() {      Row() {        Text('任务管理').fontSize(20).fontWeight(FontWeight.Bold)        Blank()        Button('+ 新增').onClick(() => this.addTask())      }.width('100%').padding(16)      if (this.isLoading) {        LoadingProgress().width(40).height(40)      } else {        List({ space: 8 }) {          ForEach(this.tasks, (task: Task) => {            ListItem() {              Row() {                Column() {                  Text(task.title)                    .fontSize(16)                    .decoration({                      type: task.status === 2 ? TextDecorationType.LineThrough : TextDecorationType.None                    })                  Text(`优先级: ${'★'.repeat(task.priority)}`)                    .fontSize(12).fontColor('#888')                }                .alignItems(HorizontalAlign.Start)                .layoutWeight(1)                Button(task.status === 2 ? '✓ 完成' : task.status === 1 ? '进行中' : '待办')                  .fontSize(12)                  .backgroundColor(task.status === 2 ? '#4CAF50' : '#FF9800')                  .onClick(() => this.toggleStatus(task.id!, task.status))                Button('删除').fontSize(12).fontColor('#F44336')                  .backgroundColor(Color.Transparent)                  .onClick(() => this.removeTask(task.id!))              }              .padding(12)              .backgroundColor('#FAFAFA')              .borderRadius(8)            }          })        }        .width('100%')        .padding({ left: 16, right: 16 })      }    }    .width('100%').height('100%')  }}

六、性能优化技巧

场景
优化手段
效果
大量数据查询
分页:predicates.limitAs(20).offsetAs(page * 20)
避免 OOM
全文搜索
创建索引:CREATE INDEX idx_title ON task(title)
查询速度 5-10x
频繁小写入
WAL 模式(默认开启)
并发读写不阻塞
复杂多表查询
store.querySql()
 执行原生 SQL
绕过 Predicates 限制
数据量统计
SELECT COUNT(*) FROM task WHERE status=?
避免加载全量数据
// 分页查询示例async findPage(page: number, pageSize: number = 20, status?: number): Promise<Task[]> {  const predicates = new relationalStore.RdbPredicates(TABLE);  if (status !== undefined) predicates.equalTo('status', status);  predicates    .orderByDesc('priority')    .limitAs(pageSize)    .offsetAs(page * pageSize);  const cursor = await this.store.query(predicates, ['*']);  const results = this.cursorToTasks(cursor);  cursor.close();  return results;}// 原生 SQL 联表查询async findTasksWithCategory(): Promise<relationalStore.ResultSet> {  const sql = `    SELECT t.*, c.name AS category_name    FROM task t    LEFT JOIN category c ON t.category_id = c.id    WHERE t.status != 2    ORDER BY t.priority DESC    LIMIT 50`;  return await this.store.querySql(sql, []);}

七、常见坑位汇总

编号
问题
原因
解法
坑1
getRdbStore
 报错 context 无效
在组件层调用
移至 UIAbility.onCreate
坑2
查询崩溃 / 内存泄漏
ResultSet 未关闭
每次查询后调用 cursor.close()
坑3
ALTER TABLE
 失败
尝试删除/修改列
仅用 ADD COLUMN,复杂改造走迁移流程
坑4
批量插入超慢
未使用事务
beginTransaction
 + commit 包裹批量操作
坑5
字段映射混乱
JS camelCase vs SQL snake_case
ValuesBucket 统一用 snake_case,Model 层做转换
坑6
多线程数据库崩溃
多个 Store 实例并发写
使用单例 DbManager,所有操作串行或加锁

总结

RelationalStore 是鸿蒙本地化数据存储的核心能力,掌握它意味着你的应用可以脱离网络独立运作。本文梳理了以下关键点:

  1. 初始化时机
    :在 UIAbility.onCreate 中初始化数据库
  2. Repository 模式
    :隔离 UI 与数据库,提升可测试性
  3. 事务是批量操作的必选项
    :性能提升数十倍
  4. 迁移脚本版本化管理
    :MIGRATIONS 字典逐版升级,绝不丢数据
  5. 分页 + 索引
    :大数据量场景的性能保障

完整代码已上传至:GitHub - ArkTS-RelationalStore-Demo

如果本文对你有帮助,欢迎点赞收藏,评论区交流踩过的坑 👇


标签:HarmonyOS ArkTS 数据库 RelationalStore SQLite 鸿蒙开发

最新文章

随机文章