文章目录
前言
有些数据不能丢:用户的笔记、离线缓存的订单、本地的聊天记录。这些得存进本地数据库,关掉 app 再打开还在。
HarmonyOS 提供了 relationalStore(关系型数据库,底层就是 SQLite)。这篇文章我用一个"待办事项"的小例子,把建库、建表、增删改查完整走一遍。跟着敲,你就能上手本地数据库。

第一步:建库
typescript
import { relationalStore } from '@kit.ArkData'
import { BusinessError } from '@kit.BasicServicesKit'
let db: relationalStore.RdbStore
async function initDB() {
const cfg: relationalStore.StoreConfig = {
name: 'todo.db', // 数据库文件名
securityLevel: relationalStore.SecurityLevel.S1
}
db = await relationalStore.getRdbStore(getContext(), cfg)
}

讲解:
getRdbStore是异步的,拿到一个RdbStore实例(就是你的"数据库连接")。securityLevel是安全等级,普通本地数据用S1足够;涉及隐私的用更高等级。- 记得
await,建库是耗时的。
第二步:建表
表结构用 SQL 语句定义。建议在 initDB 之后执行一次建表:
代码实现
typescript
await db.executeSql(
`CREATE TABLE IF NOT EXISTS todo (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER DEFAULT 0,
createTime TEXT
)`
)

IF NOT EXISTS:保证重复调用不会报错(app 第二次启动表已存在)。done INTEGER:SQLite 没有布尔,用 0/1 表示未完成/完成。- 字段类型
TEXT/INTEGER是标准 SQL,写过别的数据库的一看就懂。
第三步:增(插入)
用 ValuesBucket 装一行数据,再 insert:
核心代码
typescript
import { ValuesBucket } from '@kit.ArkData'
async function addTodo(title: string) {
const value: ValuesBucket = {
title: title,
done: 0,
createTime: new Date().toISOString()
}
await db.insert('todo', value)
}
ValuesBucket 本质是个键值对对象,键对应表里字段名。插入后自增 id 自动生成。
第四步:查(查询)
typescript
async function getAll(): Promise<Todo[]> {
const predicates = new relationalStore.RdbPredicates('todo')
predicates.orderByDesc('createTime') // 按时间倒序
const result = await db.query(predicates, ['id', 'title', 'done'])
// result 是 ResultSet,需要手动转成数组
const list: Todo[] = []
while (result.goToNextRow()) {
list.push({
id: result.getLong(result.getColumnIndex('id')),
title: result.getString(result.getColumnIndex('title')),
done: result.getLong(result.getColumnIndex('done')) === 1
})
}
result.close()
return list
}
要点:
RdbPredicates是查询条件构造器,orderByDesc排序。query返回的是ResultSet(游标),要用goToNextRow()+getXXX()一行行取,最后close()释放。- 转成普通数组后,UI 才好用
ForEach渲染。
第五步:改和删
typescript
async function toggle(id: number, done: boolean) {
const value: ValuesBucket = { done: done ? 1 : 0 }
const p = new relationalStore.RdbPredicates('todo')
p.equalTo('id', id)
await db.update(value, p) // 按条件更新
}
async function remove(id: number) {
const p = new relationalStore.RdbPredicates('todo')
p.equalTo('id', id)
await db.delete(p) // 按条件删除
}
- 改和删都用
RdbPredicates指定"哪一行"(equalTo('id', id))。 - 更新时只放要改的字段,其余不动。
常见误区(小白必踩)
| 误区 | 说明 |
|---|---|
建表漏 IF NOT EXISTS |
app 第二次启动表已存在,再 CREATE 直接报错。务必加上。 |
ResultSet 不 close() |
查询返回的游标占资源,用完必须 close(),否则泄漏。 |
| 忘了主键自增 | 没设 AUTOINCREMENT,插入重复 id 会冲突。需要唯一标识就用自增主键。 |
下面这段代码可以直接复制到 DevEco Studio 里运行。建议你边读边敲,改一改文末「动手改一改」里的参数,亲眼看看效果。
完整可运行示例:relationalStore 建表与增查
演示本地关系型数据库的最小闭环:建表、插入、查询。
完整示例
typescript
// 关系型数据库:建表 + 插入 + 查询
import { relationalStore } from '@kit.ArkData'
const SQL_CREATE = `CREATE TABLE IF NOT EXISTS user (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
)`
async function demo(db: relationalStore.RdbStore) {
await db.executeSql(SQL_CREATE) // 建表(幂等)
await db.insert('user', { name: '小明', age: 18 }) // 插入一行
const rs = await db.query('user', ['name', 'age']) // 查询
while (rs.goToNextRow()) {
console.info('name=' + rs.getString(0) + ' age=' + rs.getLong(1))
}
rs.close()
}
你会看到什么 :运行后 Log 里打印出 name=小明 age=18。表已持久化,下次启动应用数据还在。
动手改一改:
- 把
age: 18改成age: 20,再查一次,验证已落库。 - 用
db.delete('user', ...)实现按条件删除。 - 给查询加
WHERE age > 16的 predicates,做条件过滤。
写在最后
relationalStore 看着 API 多,其实就五个动作:建库、建表、增、查、改删。它和普通 SQLite 一模一样,会 SQL 就会这个。适合存"结构固定、要查询排序"的数据。
如果只是存一个用户名、一个开关,用不着上数据库------下一篇讲的 Preferences 更轻。选对存储方式,比会写代码更重要。