HarmonyOS 新手入门:ArkData 关系型数据库,别只会 SELECT *

举报
蓝瘦的蜕变 发表于 2026/06/29 14:42:52 2026/06/29
【摘要】 上一篇我们已经把 RDB 的建库、建表、插入和读取跑通了。但真实业务里,很少只是“全部读出来”。更常见的是:按关键字搜、按状态筛、按优先级排序,再顺手改一条、删一批。 这篇就把这些操作串起来,重点看 RdbPredicates。 官方文档可以先放在手边: 通过关系型数据库实现数据持久化 relationalStore

HarmonyOS 新手入门:ArkData 关系型数据库,别只会 SELECT *

上一篇我们已经把 RDB 的建库、建表、插入和读取跑通了。但真实业务里,很少只是“全部读出来”。更常见的是:按关键字搜、按状态筛、按优先级排序,再顺手改一条、删一批。

这篇就把这些操作串起来,重点看 RdbPredicates

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

先说结论

会建表和插入,只能算 RDB 入门第一步。

真正做业务列表时,绕不开查询条件、排序、更新和删除。RdbPredicates 就是这篇的主角,它可以把条件一步步拼出来,避免新手一上来就手写一整段 SQL。

它怎么用

你可以把它当成“查询条件对象”。不想手写一整段 SQL 时,就用它把条件一步步拼出来。

比如这个 Demo 里,我做了几个很常见的条件:

const predicates = new relationalStore.RdbPredicates(TABLE_TASK);
if (this.keyword.length > 0) {
  predicates.like('title', `%${this.keyword}%`);
}
if (this.onlyTodo) {
  predicates.equalTo('status', 'todo');
}
predicates.orderByDesc('priority');
predicates.orderByDesc('id');
predicates.limitAs(20);

这里有个小点:当前 SDK 里限制数量用的是 limitAs(20)。如果你看旧文章写 limit(20),建议以本机 SDK 声明为准。

这个 Demo 做什么

页面里可以新增任务、选择优先级、输入关键字查询,也可以只看待办任务。

任务右侧有一个按钮,用来在 tododone 之间切换。更新时也是先用 RdbPredicates 找到那一条,再调用 update

const values: relationalStore.ValuesBucket = {
  status: item.status === 'done' ? 'todo' : 'done'
};
await store.update(values, predicates);

删除已完成任务也是同样的思路:

predicates.equalTo('status', 'done');
await store.delete(predicates);

这比直接拼 SQL 更适合新手读,也方便后面把查询条件单独抽出去。

关键代码

点击“查询”会打开半模态弹框,里面展示当前数据库、表名、查询关键字、筛选状态和结果数量。

列表只展示当前查询结果,不做 LazyForEach 懒加载渲染处理。这个 Demo 的重点是 CRUD,不是长列表优化。

欢迎评论区一起分享~

你们做列表查询时,最常见的条件是什么?状态、关键字、时间范围、分页,还是多条件组合?如果你的项目里查询条件很多,会直接写在页面里,还是会把 RdbPredicates 封装成一个方法?

完整示例代码

文件位置:ArkDataRdbCrudDemo.ets

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

const DB_NAME: string = 'rdb_crud_demo.db';
const TABLE_TASK: string = 'crud_tasks';

class CrudTask {
  id: number = 0;
  title: string = '';
  priority: number = 1;
  status: string = '';

  constructor(id: number, title: string, priority: number, status: string) {
    this.id = id;
    this.title = title;
    this.priority = priority;
    this.status = status;
  }
}

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

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

@Entry
@Component
struct ArkDataRdbCrudDemo {
  @State title: string = '';
  @State keyword: string = '';
  @State onlyTodo: boolean = false;
  @State priority: number = 1;
  @State tips: string = '用 RdbPredicates 做查询、更新和删除';
  @State tasks: CrudTask[] = [];
  @State showResultSheet: boolean = false;
  @State infoItems: CrudInfoItem[] = [];

  private store: relationalStore.RdbStore | undefined = undefined;

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

  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_TASK} (` +
        'id INTEGER PRIMARY KEY AUTOINCREMENT, ' +
        'title TEXT NOT NULL, ' +
        'priority INTEGER NOT NULL, ' +
        'status TEXT NOT NULL)'
    );
    this.store = store;
    return store;
  }

  private buildPredicates(): relationalStore.RdbPredicates {
    const predicates = new relationalStore.RdbPredicates(TABLE_TASK);
    if (this.keyword.length > 0) {
      predicates.like('title', `%${this.keyword}%`);
    }
    if (this.onlyTodo) {
      predicates.equalTo('status', 'todo');
    }
    predicates.orderByDesc('priority');
    predicates.orderByDesc('id');
    predicates.limitAs(20);
    return predicates;
  }

  private updateInfoItems(): void {
    this.infoItems = [
      new CrudInfoItem('数据库名称', DB_NAME),
      new CrudInfoItem('表名', TABLE_TASK),
      new CrudInfoItem('查询关键字', this.keyword.length > 0 ? this.keyword : '未输入'),
      new CrudInfoItem('只看待办', this.onlyTodo ? '是' : '否'),
      new CrudInfoItem('当前结果数', `${this.tasks.length}`),
      new CrudInfoItem('说明', '列表只展示当前查询结果,不做 LazyForEach 懒加载渲染处理')
    ];
  }

  private async queryTasks(): Promise<void> {
    try {
      const store = await this.getStore();
      const resultSet = await store.query(
        this.buildPredicates(),
        ['id', 'title', 'priority', 'status']
      );
      const rows: CrudTask[] = [];
      const idIndex = resultSet.getColumnIndex('id');
      const titleIndex = resultSet.getColumnIndex('title');
      const priorityIndex = resultSet.getColumnIndex('priority');
      const statusIndex = resultSet.getColumnIndex('status');
      while (resultSet.goToNextRow()) {
        rows.push(new CrudTask(
          resultSet.getLong(idIndex),
          resultSet.getString(titleIndex),
          resultSet.getLong(priorityIndex),
          resultSet.getString(statusIndex)
        ));
      }
      resultSet.close();
      this.tasks = rows;
      this.updateInfoItems();
      this.tips = rows.length > 0 ? '查询成功:已按条件刷新列表' : '没有匹配的数据';
    } catch (err) {
      this.tips = '查询失败,请查看日志';
      promptAction.showToast({ message: '查询失败' });
      console.error('RDB CRUD query failed');
    }
  }

  private async addTask(): Promise<void> {
    try {
      const store = await this.getStore();
      const title = this.title.length > 0 ? this.title : '整理 RDB 查询条件';
      const values: relationalStore.ValuesBucket = {
        title: title,
        priority: this.priority,
        status: 'todo'
      };
      await store.insert(TABLE_TASK, values);
      this.title = '';
      await this.queryTasks();
      this.tips = '保存成功:已插入一条任务';
      promptAction.showToast({ message: '任务已保存' });
    } catch (err) {
      this.tips = '保存失败,请查看日志';
      promptAction.showToast({ message: '保存失败' });
      console.error('RDB CRUD insert failed');
    }
  }

  private async toggleTask(item: CrudTask): Promise<void> {
    try {
      const store = await this.getStore();
      const predicates = new relationalStore.RdbPredicates(TABLE_TASK);
      predicates.equalTo('id', item.id);
      const values: relationalStore.ValuesBucket = {
        status: item.status === 'done' ? 'todo' : 'done'
      };
      await store.update(values, predicates);
      await this.queryTasks();
      promptAction.showToast({ message: '状态已更新' });
    } catch (err) {
      this.tips = '更新失败,请查看日志';
      promptAction.showToast({ message: '更新失败' });
      console.error('RDB CRUD update failed');
    }
  }

  private async deleteDone(): Promise<void> {
    try {
      const store = await this.getStore();
      const predicates = new relationalStore.RdbPredicates(TABLE_TASK);
      predicates.equalTo('status', 'done');
      const count = await store.delete(predicates);
      await this.queryTasks();
      this.tips = `已删除 ${count} 条已完成任务`;
      promptAction.showToast({ message: '已删除完成项' });
    } catch (err) {
      this.tips = '删除失败,请查看日志';
      promptAction.showToast({ message: '删除失败' });
      console.error('RDB CRUD delete failed');
    }
  }

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

  @Builder
  private queryInfoSheet() {
    Column({ space: 14 }) {
      Text('RDB 条件查询结果')
        .width('100%')
        .fontSize(22)
        .fontWeight(700)
        .fontColor('#0F172A')

      Text('这里展示 RdbPredicates 查询条件和当前结果,不做 LazyForEach 懒加载渲染处理。')
        .width('100%')
        .fontSize(13)
        .fontColor('#64748B')

      Column({ space: 8 }) {
        ForEach(this.infoItems, (item: CrudInfoItem) => {
          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: CrudInfoItem) => item.label)
      }
      .width('100%')

      Button('关闭')
        .width('100%')
        .height(44)
        .fontSize(16)
        .backgroundColor('#2563EB')
        .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('#2563EB')
            .onClick(() => {
              router.back();
            })
          Blank()
        }
        .width('100%')

        Text('RDB 条件查询')
          .width('100%')
          .fontSize(28)
          .fontWeight(700)
          .fontColor('#182431')

        Text('用 RdbPredicates 完成查询、更新和删除。')
          .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('优先级 1')
            .layoutWeight(1)
            .height(40)
            .fontSize(13)
            .fontColor(this.priority === 1 ? '#FFFFFF' : '#0F172A')
            .backgroundColor(this.priority === 1 ? '#2563EB' : '#DBEAFE')
            .onClick(() => {
              this.priority = 1;
            })
          Button('优先级 2')
            .layoutWeight(1)
            .height(40)
            .fontSize(13)
            .fontColor(this.priority === 2 ? '#FFFFFF' : '#0F172A')
            .backgroundColor(this.priority === 2 ? '#2563EB' : '#DBEAFE')
            .onClick(() => {
              this.priority = 2;
            })
          Button('优先级 3')
            .layoutWeight(1)
            .height(40)
            .fontSize(13)
            .fontColor(this.priority === 3 ? '#FFFFFF' : '#0F172A')
            .backgroundColor(this.priority === 3 ? '#2563EB' : '#DBEAFE')
            .onClick(() => {
              this.priority = 3;
            })
        }
        .width('100%')

        TextInput({ placeholder: '按标题关键字查询', text: this.keyword })
          .height(44)
          .fontSize(16)
          .backgroundColor('#EFF6FF')
          .fontColor('#0F172A')
          .onChange((value: string) => {
            this.keyword = value;
          })

        Row({ space: 10 }) {
          Button('保存')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .backgroundColor('#2563EB')
            .onClick(() => {
              this.addTask();
            })
          Button(this.onlyTodo ? '只看待办' : '全部状态')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#0F172A')
            .backgroundColor('#BFDBFE')
            .onClick(() => {
              this.onlyTodo = !this.onlyTodo;
              this.queryTasks();
            })
          Button('查询')
            .layoutWeight(1)
            .height(44)
            .fontSize(15)
            .fontColor('#0F172A')
            .backgroundColor('#CBD5E1')
            .onClick(() => {
              this.showQueryInfo();
            })
        }
        .width('100%')

        Button('删除已完成')
          .width('100%')
          .height(42)
          .fontSize(15)
          .fontColor('#0F172A')
          .backgroundColor('#E2E8F0')
          .onClick(() => {
            this.deleteDone();
          })

        Column({ space: 8 }) {
          ForEach(this.tasks, (item: CrudTask) => {
            Row() {
              Column({ space: 4 }) {
                Text(item.title)
                  .fontSize(17)
                  .fontWeight(700)
                  .fontColor('#0F172A')
                Text(`id=${item.id} · priority=${item.priority} · ${item.status}`)
                  .fontSize(12)
                  .fontColor('#64748B')
              }
              .alignItems(HorizontalAlign.Start)
              .layoutWeight(1)

              Button(item.status === 'done' ? '恢复' : '完成')
                .height(34)
                .fontSize(13)
                .backgroundColor(item.status === 'done' ? '#CBD5E1' : '#2563EB')
                .onClick(() => {
                  this.toggleTask(item);
                })
            }
            .width('100%')
            .padding(14)
            .backgroundColor('#F8FAFC')
            .borderRadius(8)
          }, (item: CrudTask) => `${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.queryInfoSheet(), {
      height: SheetSize.MEDIUM,
      dragBar: true,
      showClose: true,
      onDisappear: () => {
        this.showResultSheet = false;
      }
    })
  }
}
【版权声明】本文为华为云社区用户原创内容,未经允许不得转载,如需转载请自行联系原作者进行授权。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

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

全部回复

上滑加载中

设置昵称

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

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

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