HarmonyOS 新手入门:ArkData 关系型数据库,从一条待办开始
HarmonyOS 新手入门:ArkData 关系型数据库,从一条待办开始
前面讲 Preferences 和 KVStore 时,我们存的都是比较轻的数据。到了“有字段、有列表、后面还可能按条件查”的时候,就不要硬塞键值对了,直接上关系型数据库更舒服。
这篇先不铺太多概念,只做一个待办页。能建库、建表、保存、读取,就算入门跑通。
官方文档可以先放在手边:
先说结论
只要数据开始有字段、有列表、有查询条件,就不要再硬塞进 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;
}
})
}
}
- 点赞
- 收藏
- 关注作者
评论(0)