一、背景说明

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。
六、扩展建议

在本文基础上可以继续演进:

  1. 云端同步:将 StoreConfig 配合 distributedDB 能力,把同一张表在设备间同步;
  2. 向量检索:API 18+ 支持 vector: true 的向量库,可结合端侧 AI 做语义搜索;
  3. 备份恢复:调用 backup() / restore() 在升级迁移前做库文件快照:
await rdbStore.backup('todo_backup.db'); // 一行完成备份
结语

本文用 relationalStore 完成了鸿蒙应用本地数据库从建库建表到增删改查、事务批量写入的完整实现,核心代码约 150 行,全部可直接编译运行。掌握 RDB Store 后,应用内的列表缓存、离线数据、结构化配置都有了一个可靠且可扩展的落地方案。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐