HarmonyOS 新手入门:ArkData 关系型数据库,别只会 SELECT *
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 做什么
页面里可以新增任务、选择优先级、输入关键字查询,也可以只看待办任务。
任务右侧有一个按钮,用来在 todo 和 done 之间切换。更新时也是先用 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;
}
})
}
}
- 点赞
- 收藏
- 关注作者
评论(0)