HarmonyOS 新手入门:ArkData 关系型数据库,从一条待办开始

举报
蓝瘦的蜕变 发表于 2026/06/27 11:35:24 2026/06/27
【摘要】 前面讲 Preferences 和 KVStore 时,我们存的都是比较轻的数据。到了“有字段、有列表、后面还可能按条件查”的时候,就不要硬塞键值对了,直接上关系型数据库更舒服。 这篇先不铺太多概念,只做一个待办页。能建库、建表、保存、读取,就算入门跑通。 官方文档可以先放在手边: 通过关系型数据库实现数据持久化 re

HarmonyOS 新手入门:ArkData 关系型数据库,从一条待办开始

前面讲 PreferencesKVStore 时,我们存的都是比较轻的数据。到了“有字段、有列表、后面还可能按条件查”的时候,就不要硬塞键值对了,直接上关系型数据库更舒服。

这篇先不铺太多概念,只做一个待办页。能建库、建表、保存、读取,就算入门跑通。

官方文档可以先放在手边:

先说结论

只要数据开始有字段、有列表、有查询条件,就不要再硬塞进 key-value。

RDB 入门先抓住三件事:

  • 打开数据库。
  • 建一张清楚的小表。
  • insert 保存,用查询接口读出来。

它怎么用

RDB 的入口是 relationalStore.getRdbStore。这里最少要给数据库名和安全级别:

const config: relationalStore.StoreConfig = {
  name: 'rdb_todo_demo.db',
  securityLevel: relationalStore.SecurityLevel.S1
};
const store = await relationalStore.getRdbStore(context, config);

securityLevel 入门 Demo 用 S1 就够了。真实项目里如果涉及敏感信息,别照抄,按数据级别重新选。

表结构别太复杂

这次只建一张 todo_items 表:

await store.executeSql(
  'CREATE TABLE IF NOT EXISTS todo_items (' +
    'id INTEGER PRIMARY KEY AUTOINCREMENT, ' +
    'title TEXT NOT NULL, ' +
    'done INTEGER NOT NULL, ' +
    'created_at TEXT NOT NULL)'
);

几个字段就够用:id 做主键,title 存标题,done 存完成状态,created_at 存创建时间。新手 Demo 最怕一上来表结构过度设计,最后重点反而被淹没。

关键代码

保存用 insert,读取用 querySql。读取时会拿到 ResultSet,用完记得 close()

这个细节很小,但值得一开始就养成习惯。很多数据库相关的问题,最后都不是 API 不会用,而是资源没有收尾。

这个 Demo 做什么

Demo 页面提供三个按钮:保存、读取、清空。

点击“读取”时会弹出半模态面板,里面展示数据库名、表名、字段和当前记录数。页面列表只展示当前保存的数据库信息,不做 LazyForEach 懒加载渲染处理,因为这篇的重点不是大列表性能,而是先把 RDB 基本链路看清楚。

欢迎评论区一起分享~

你们第一次用关系型数据库,一般会先存什么?待办、账单、收藏列表,还是搜索历史?如果你对字段拆分有自己的习惯,也可以聊聊你会怎么设计这张表。

完整示例代码

文件位置:ArkDataRdbTodoDemo.ets

import { common } from '@kit.AbilityKit';
import { relationalStore } from '@kit.ArkData';
import { promptAction, router } from '@kit.ArkUI';

const DB_NAME: string = 'rdb_todo_demo.db';
const TABLE_TODO: string = 'todo_items';

class TodoItem {
  id: number = 0;
  title: string = '';
  done: number = 0;
  createdAt: string = '';

  constructor(id: number, title: string, done: number, createdAt: string) {
    this.id = id;
    this.title = title;
    this.done = done;
    this.createdAt = createdAt;
  }
}

class RdbInfoItem {
  label: string = '';
  value: string = '';

  constructor(label: string, value: string) {
    this.label = label;
    this.value = value;
  }
}

@Entry
@Component
struct ArkDataRdbTodoDemo {
  @State title: string = '';
  @State tips: string = '启动后会打开 RDB,并创建一张待办表';
  @State todoItems: TodoItem[] = [];
  @State showResultSheet: boolean = false;
  @State infoItems: RdbInfoItem[] = [];

  private store: relationalStore.RdbStore | undefined = undefined;

  aboutToAppear(): void {
    this.loadTodos();
  }

  private async getStore(): Promise<relationalStore.RdbStore> {
    if (this.store !== undefined) {
      return this.store;
    }

    const context = getContext(this) as common.UIAbilityContext;
    const config: relationalStore.StoreConfig = {
      name: DB_NAME,
      securityLevel: relationalStore.SecurityLevel.S1
    };
    const store = await relationalStore.getRdbStore(context, config);
    await store.executeSql(
      `CREATE TABLE IF NOT EXISTS ${TABLE_TODO} (` +
        'id INTEGER PRIMARY KEY AUTOINCREMENT, ' +
        'title TEXT NOT NULL, ' +
        'done INTEGER NOT NULL, ' +
        'created_at TEXT NOT NULL)'
    );
    this.store = store;
    return store;
  }

  private updateInfoItems(): void {
    this.infoItems = [
      new RdbInfoItem('数据库名称', DB_NAME),
      new RdbInfoItem('表名', TABLE_TODO),
      new RdbInfoItem('字段', 'id, title, done, created_at'),
      new RdbInfoItem('当前记录数', `${this.todoItems.length}`),
      new RdbInfoItem('说明', '列表只展示当前查询结果,不做 LazyForEach 懒加载渲染处理')
    ];
  }

  private async loadTodos(): Promise<void> {
    try {
      const store = await this.getStore();
      const resultSet = await store.querySql(
        `SELECT id, title, done, created_at FROM ${TABLE_TODO} ORDER BY id DESC`
      );
      const rows: TodoItem[] = [];
      const idIndex = resultSet.getColumnIndex('id');
      const titleIndex = resultSet.getColumnIndex('title');
      const doneIndex = resultSet.getColumnIndex('done');
      const createdAtIndex = resultSet.getColumnIndex('created_at');
      while (resultSet.goToNextRow()) {
        rows.push(new TodoItem(
          resultSet.getLong(idIndex),
          resultSet.getString(titleIndex),
          resultSet.getLong(doneIndex),
          resultSet.getString(createdAtIndex)
        ));
      }
      resultSet.close();
      this.todoItems = rows;
      this.updateInfoItems();
      this.tips = rows.length > 0 ? '读取成功:当前展示数据库里的待办记录' : '当前表里还没有待办';
    } catch (err) {
      this.tips = '读取失败,请查看日志';
      promptAction.showToast({ message: '读取失败' });
      console.error('RDB todo load failed');
    }
  }

  private async addTodo(): Promise<void> {
    try {
      const store = await this.getStore();
      const title = this.title.length > 0 ? this.title : '学习 RDB 建表';
      const values: relationalStore.ValuesBucket = {
        title: title,
        done: 0,
        created_at: new Date().toString()
      };
      await store.insert(TABLE_TODO, values);
      this.title = '';
      await this.loadTodos();
      this.tips = '保存成功:已插入一条待办';
      promptAction.showToast({ message: '待办已保存' });
    } catch (err) {
      this.tips = '保存失败,请查看日志';
      promptAction.showToast({ message: '保存失败' });
      console.error('RDB todo insert failed');
    }
  }

  private async clearTodos(): Promise<void> {
    try {
      const store = await this.getStore();
      await store.executeSql(`DELETE FROM ${TABLE_TODO}`);
      await this.loadTodos();
      this.tips = '已清空待办表';
      promptAction.showToast({ message: '已清空' });
    } catch (err) {
      this.tips = '清空失败,请查看日志';
      promptAction.showToast({ message: '清空失败' });
      console.error('RDB todo clear failed');
    }
  }

  private async showDbInfo(): Promise<void> {
    await this.loadTodos();
    this.showResultSheet = true;
  }

  @Builder
  private dbInfoSheet() {
    Column({ space: 14 }) {
      Text('RDB 数据库信息')
        .width('100%')
        .fontSize(22)
        .fontWeight(700)
        .fontColor('#0F172A')

      Text('下面列表展示当前 Demo 的数据库、表和查询结果信息,不做 LazyForEach 懒加载渲染处理。')
        .width('100%')
        .fontSize(13)
        .fontColor('#64748B')

      Column({ space: 8 }) {
        ForEach(this.infoItems, (item: RdbInfoItem) => {
          Column({ space: 4 }) {
            Text(item.label)
              .fontSize(12)
              .fontColor('#64748B')
            Text(item.value)
              .fontSize(16)
              .fontColor('#0F172A')
          }
          .width('100%')
          .padding(12)
          .backgroundColor('#F8FAFC')
          .borderRadius(8)
        }, (item: RdbInfoItem) => item.label)
      }
      .width('100%')

      Button('关闭')
        .width('100%')
        .height(44)
        .fontSize(16)
        .backgroundColor('#0EA5E9')
        .onClick(() => {
          this.showResultSheet = false;
        })
    }
    .width('100%')
    .padding({ left: 20, right: 20, top: 12, bottom: 20 })
  }

  build() {
    Scroll() {
      Column({ space: 18 }) {
        Row() {
          Text('返回')
            .fontSize(14)
            .fontColor('#0EA5E9')
            .onClick(() => {
              router.back();
            })
          Blank()
        }
        .width('100%')

        Text('RDB 入门待办')
          .width('100%')
          .fontSize(28)
          .fontWeight(700)
          .fontColor('#182431')

        Text('创建数据库、创建表,并插入一条待办数据。')
          .width('100%')
          .fontSize(14)
          .fontColor('#56616F')

        TextInput({ placeholder: '输入待办标题', text: this.title })
          .height(44)
          .fontSize(16)
          .backgroundColor('#F1F5F9')
          .fontColor('#0F172A')
          .onChange((value: string) => {
            this.title = value;
          })

        Row({ space: 10 }) {
          Button('保存')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .backgroundColor('#0EA5E9')
            .onClick(() => {
              this.addTodo();
            })

          Button('读取')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#0F172A')
            .backgroundColor('#BAE6FD')
            .onClick(() => {
              this.showDbInfo();
            })

          Button('清空')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#0F172A')
            .backgroundColor('#CBD5E1')
            .onClick(() => {
              this.clearTodos();
            })
        }
        .width('100%')

        Column({ space: 8 }) {
          ForEach(this.todoItems, (item: TodoItem) => {
            Column({ space: 6 }) {
              Text(item.title)
                .width('100%')
                .fontSize(17)
                .fontWeight(700)
                .fontColor('#0F172A')
              Text(`id=${item.id} · done=${item.done} · ${item.createdAt}`)
                .width('100%')
                .fontSize(12)
                .fontColor('#64748B')
            }
            .width('100%')
            .padding(14)
            .backgroundColor('#F8FAFC')
            .borderRadius(8)
          }, (item: TodoItem) => `${item.id}`)
        }
        .width('100%')

        Text(this.tips)
          .width('100%')
          .fontSize(13)
          .fontColor('#64748B')
      }
      .width('100%')
      .padding(24)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FFFFFF')
    .bindSheet(this.showResultSheet, this.dbInfoSheet(), {
      height: SheetSize.MEDIUM,
      dragBar: true,
      showClose: true,
      onDisappear: () => {
        this.showResultSheet = false;
      }
    })
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。