HarmonyOS SQLite(relationalStore)增删改查指南
针对:【https://harmonyosdev.csdn.net/user/discuss/6a938fc33bda720d4b37c1e2】
的问答回复。
基于本项目(xiangcejihe)中 30+ 个 DAO 的真实代码总结,涉及
UserDao、TodoDao、ContactDao、BackupDao等,示例均取自entry/src/main/ets/database/下的实际实现。
一、核心概念
| 概念 | 说明 |
|---|---|
relationalStore | ArkData 数据管理套件中的关系型数据库模块(@kit.ArkData),底层是 SQLite |
RdbStore | 数据库实例,所有增删改查都在它上面执行 |
StoreConfig | 数据库配置:文件名 + 安全级别 |
RdbPredicates | 查询/更新/删除的条件构造器(WHERE 子句) |
ValuesBucket | 插入/更新时的「列名 → 值」键值对 |
ResultSet | 查询结果集,需逐行遍历后手动 close |
import { relationalStore } from '@kit.ArkData';
import { common } from '@kit.AbilityKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
二、建库与建表(初始化)
本项目中所有 DAO 都遵循同一套模式:单例复用 + CREATE TABLE IF NOT EXISTS + 建索引,可重复调用不会报错。
export class UserDao {
private store: relationalStore.RdbStore | null = null;
constructor(private context: common.Context) {}
/** 初始化数据库:获取 RdbStore 实例并建表,可重复调用 */
async init(): Promise<void> {
if (this.store !== null) {
return;
}
const config: relationalStore.StoreConfig = {
name: 'demo.db', // 数据库文件名,存于应用沙箱内
securityLevel: relationalStore.SecurityLevel.S1, // S1~S4,S1 最低安全级别
};
this.store = await relationalStore.getRdbStore(this.context, config);
// 建表(IF NOT EXISTS 保证幂等)
await this.store.executeSql(
'CREATE TABLE IF NOT EXISTS user (' +
'id INTEGER PRIMARY KEY AUTOINCREMENT, ' + // 自增主键
'name TEXT NOT NULL, ' +
'age INTEGER, ' +
'phone TEXT' +
')'
);
// 为高频查询字段建索引(TodoDao 的实战做法)
await this.store.executeSql(
'CREATE INDEX IF NOT EXISTS idx_user_name ON user (name)'
);
}
}
要点:
- 数据库文件保存在应用沙箱内,无需任何权限声明。
getRdbStore是 async 的;securityLevel可选S1/S2/S3/S4,普通业务数据用S1即可。- 静态单例版(
TodoDao/ContactDao的写法):用private static store?: relationalStore.RdbStore缓存,getStore(context)中判空复用。
三、增(INSERT)
/** 新增一条记录,返回自增 id */
async insert(name: string, age: number, phone: string): Promise<number> {
const values: relationalStore.ValuesBucket = {
'name': name,
'age': age,
'phone': phone,
};
// 注意:自增 id 不用传,insert 自动填充并返回新 id
const rowId: number = await this.store!.insert('user', values);
return rowId;
}
要点:
- 入参是
表名 + ValuesBucket,返回新记录的自增 id(插入失败会 reject)。 ValuesBucket的键必须与列名完全一致(含下划线,如created_time)。- 批量插入(种子数据 / 备份恢复场景):循环
insert,配合事务(见第七节)可大幅提升性能。
// TodoDao 种子数据批量插入
for (const s of seed) {
const values: relationalStore.ValuesBucket = {
title: s.title, priority: s.priority, category: s.category,
completed: s.completed, created_time: s.createdTime,
completed_time: s.completed === 1 ? s.createdTime : 0,
remark: s.remark,
};
await store.insert('todo', values);
}
四、删(DELETE)
删除必须用 RdbPredicates 指定条件,返回受影响行数(不是 true/false)。
/** 按 id 删除 */
async deleteById(id: number): Promise<number> {
const predicates = new relationalStore.RdbPredicates('user');
predicates.equalTo('id', id);
return await this.store!.delete(predicates);
}
/** 清空整张表:不写任何条件即可 */
async deleteAll(): Promise<number> {
const predicates = new relationalStore.RdbPredicates('user');
return await this.store!.delete(predicates);
}
五、改(UPDATE)
update(ValuesBucket, RdbPredicates):只更新 ValuesBucket 中出现的列,其余列保持不变(天然支持部分更新)。
/** 按 id 更新全部业务字段 */
async update(id: number, name: string, age: number, phone: string): Promise<number> {
const values: relationalStore.ValuesBucket = {
'name': name, 'age': age, 'phone': phone,
};
const predicates = new relationalStore.RdbPredicates('user');
predicates.equalTo('id', id);
return await this.store!.update(values, predicates); // 返回受影响行数
}
实战:只改两列的局部更新(TodoDao 切换完成状态)
/** 部分更新:只改 completed 与 completed_time */
static async toggleCompleted(context: common.Context, id: number, completed: number): Promise<number> {
const store = await TodoDao.getStore(context);
const values: relationalStore.ValuesBucket = {
completed: completed,
completed_time: completed === 1 ? Date.now() : 0,
};
const predicates = new relationalStore.RdbPredicates(TodoDao.TABLE);
predicates.equalTo('id', id);
return await store.update(values, predicates);
}
实战:按条件批量更新(CouponDao 把全部过期券置为失效)
static async expireAll(context: common.Context): Promise<number> {
const store = await CouponDao.getStore(context);
const values: relationalStore.ValuesBucket = { status: 2 };
const predicates = new relationalStore.RdbPredicates(CouponDao.TABLE);
predicates.equalTo('status', 0).lessThan('expire_time', Date.now());
return await store.update(values, predicates); // 一次更新所有过期券
}
六、查(SELECT)
6.1 条件构造器 RdbPredicates 常用方法
| 方法 | SQL 等价 | 本项目出处 |
|---|---|---|
equalTo(col, v) | col = v | 所有 DAO |
notEqualTo(col, v) | col != v | — |
like(col, '%kw%') | 模糊匹配 | UserDao / ContactDao 搜索 |
greaterThan / greaterThanOrEqualTo | > / >= | CouponDao 查未过期券 |
lessThan / lessThanOrEqualTo | < / <= | CouponDao 查已过期券 |
between(col, a, b) | BETWEEN a AND b | LedgerDao 时间段查询 |
in(col, [v1,v2]) | IN (...) | MediaDao 多类型筛选 |
isNull / isNotNull | IS NULL | — |
.and() / .or() | 条件连接 | 链式调用时默认 AND,显式 .or() 切换 OR |
orderByAsc / orderByDesc | ORDER BY,可多级 | TodoDao 多字段排序 |
limitAs(n) | LIMIT n | UserDao 查第一条 |
countAs / sumAs / avgAs / maxAs / minAs | 聚合投影列 | ContactDao GROUP BY 统计 |
// 多字段 OR 模糊搜索(ContactDao)
predicates.like('name', `%${keyword}%`)
.or()
.like('phone', `%${keyword}%`)
.orderByAsc('pinyin').orderByAsc('name');
// 时间范围查询(LedgerDao)
predicates.between('trade_time', start, end).orderByDesc('trade_time');
// IN 查询(MediaDao)
predicates.in('type', types).orderByDesc('rating');
// 多级排序(TodoDao:未完成在前 → 优先级高在前 → 新的在前)
predicates.orderByAsc('completed').orderByDesc('priority').orderByDesc('created_time');
6.2 查询全部 / 查询单条
/** 查询全部,按 id 倒序 */
async queryAll(): Promise<User[]> {
const predicates = new relationalStore.RdbPredicates('user');
predicates.orderByDesc('id');
const resultSet: relationalStore.ResultSet = await this.store!.query(predicates);
const users: User[] = this.parseResultSet(resultSet);
resultSet.close(); // 必须关闭,防止内存泄漏
return users;
}
/** 查询第一条,无记录返回 null(limitAs(1) 的用法) */
async queryFirst(): Promise<User | null> {
const predicates = new relationalStore.RdbPredicates('user');
predicates.limitAs(1);
const resultSet = await this.store!.query(predicates);
let user: User | null = null;
if (resultSet.goToNextRow()) {
user = this.rowToUser(resultSet);
}
resultSet.close();
return user;
}
6.3 结果集遍历与行转对象
// 逐行遍历
private parseResultSet(resultSet: relationalStore.ResultSet): User[] {
const users: User[] = [];
while (resultSet.goToNextRow()) { // 游标移动到下一行,无数据返回 false
users.push(this.rowToUser(resultSet));
}
return users;
}
// 按列名取列下标,再按类型取值
private rowToUser(resultSet: relationalStore.ResultSet): User {
return {
id: resultSet.getLong(resultSet.getColumnIndex('id')),
name: resultSet.getString(resultSet.getColumnIndex('name')),
age: resultSet.getLong(resultSet.getColumnIndex('age')),
phone: resultSet.getString(resultSet.getColumnIndex('phone')),
};
}
getLong/getString/getFloat/getBlob按列的存储类型选择。本项目中时间戳(created_time)存INTEGER,用getLong取出。
6.4 原生 SQL 查询(querySql)
复杂统计(聚合、CASE WHEN、GROUP BY)直接写 SQL:
/** 一条 SQL 出 4 个统计值(TodoDao) */
static async statistics(context: common.Context): Promise<TodoStats> {
const store = await TodoDao.getStore(context);
const result = await store.querySql(
`SELECT COUNT(*) AS total,
SUM(CASE WHEN completed=0 THEN 1 ELSE 0 END) AS pending,
SUM(CASE WHEN completed=1 THEN 1 ELSE 0 END) AS done,
SUM(CASE WHEN priority=2 AND completed=0 THEN 1 ELSE 0 END) AS high
FROM todo`
);
let total = 0, pending = 0, done = 0, high = 0;
if (result.goToNextRow()) {
total = result.getLong(result.getColumnIndex('total'));
pending = result.getLong(result.getColumnIndex('pending'));
done = result.getLong(result.getColumnIndex('done'));
high = result.getLong(result.getColumnIndex('high'));
}
result.close();
return { total, pending, done, high };
}
/** GROUP BY 分组统计(ContactDao 拼音分组人数) */
static async statistics(context: common.Context): Promise<ContactStats> {
const store = await ContactDao.getStore(context);
const groups: Record<string, number> = {};
let total = 0;
const result = await store.querySql(
`SELECT pinyin, COUNT(*) AS cnt FROM contact GROUP BY pinyin ORDER BY pinyin`
);
while (result.goToNextRow()) {
const p = result.getString(result.getColumnIndex('pinyin'));
const cnt = result.getLong(result.getColumnIndex('cnt'));
groups[p] = cnt;
total += cnt;
}
result.close();
return { total, groups };
}
执行非查询类原生 SQL(建表、建索引)用 executeSql(sql)。
七、事务(批量操作必备)
涉及"多步写操作要么全成功要么全失败"的场景(备份恢复、菜谱+配料、保养记录+同步里程),用 beginTransaction / commit / rollBack:
/** 备份恢复:清空表 + 批量导入,任一失败整体回滚(BackupDao) */
static async restoreJson(context: common.Context, json: string): Promise<number> {
const store = await BackupDao.getStore(context);
const parsed: SourceJsonRow[] = JSON.parse(json);
let count = 0;
try {
await store.beginTransaction(); // 1. 开启事务
const delPred = new relationalStore.RdbPredicates(BackupDao.SOURCE_TABLE);
await store.delete(delPred); // 先清空
for (const r of parsed) {
const values: relationalStore.ValuesBucket = {
name: r.name, category: r.category, amount: r.amount,
note: r.note, created_time: r.createdTime,
};
await store.insert(BackupDao.SOURCE_TABLE, values);
count++;
}
await store.commit(); // 2. 全部成功 → 提交
return count;
} catch (e) {
await store.rollBack(); // 3. 任何异常 → 回滚
throw new Error(`恢复失败: ${JSON.stringify(e)}`);
}
}
八、页面层调用示例(SqlDemo.ets)
页面通过 this.getUIContext().getHostContext() 获取上下文,初始化 DAO 后调用增删改查,并用 .then/.catch 处理结果:
@Entry
@Component
struct SqlDemo {
private dao: UserDao | null = null;
@State users: User[] = [];
@State logText: string = '正在初始化数据库...';
aboutToAppear(): void {
let host = this.getUIContext().getHostContext();
if (host === undefined) { return; }
let context = host as common.UIAbilityContext;
this.dao = new UserDao(context);
this.dao.init().then(() => {
this.logText = '✅ 数据库初始化成功';
this.refresh();
}).catch((err: BusinessError) => {
this.logText = `❌ 初始化失败: ${JSON.stringify(err)}`;
});
}
/** 增 */
private onInsert(): void {
this.dao!.insert('张三', 25, '13800138000').then((rowId: number) => {
this.logText = `✅ 新增成功,自增 id = ${rowId}`;
this.refresh();
}).catch((err: BusinessError) => {
this.logText = `❌ 新增失败: ${JSON.stringify(err)}`;
});
}
/** 改(先查出目标记录,再按 id 更新) */
private onUpdateFirst(): void {
this.dao!.queryFirst().then((first: User | null) => {
if (first === null) { return; }
this.dao!.update(first.id, '李四', 30, '13900139000').then((rows: number) => {
this.logText = `✅ 已更新 id=${first.id},受影响 ${rows} 行`;
this.refresh();
});
});
}
/** 删 */
private onDeleteById(id: number): void {
this.dao!.deleteById(id).then((rows: number) => {
this.logText = `✅ 已删除 id=${id},受影响 ${rows} 行`;
this.refresh();
});
}
/** 查 + 刷新 @State 驱动 UI 刷新 */
private refresh(): void {
this.dao!.queryAll().then((list: User[]) => {
this.users = list; // 赋值 @State,列表自动刷新
});
}
build() {
Column() {
ForEach(this.users, (item: User) => {
Text(`${item.id} ${item.name} ${item.age}`)
.onClick(() => this.onDeleteById(item.id));
}, (item: User) => `${item.id}-${item.name}`)
}
}
}
排查技巧(项目实战):把查询结果
JSON.stringify到一个文本里显示出来——有 JSON 说明数据写入成功,列表没显示则是 UI 层问题。
九、API 速查表
| 操作 | API | 返回 |
|---|---|---|
| 建库/建表 | relationalStore.getRdbStore(context, config) | RdbStore(Promise) |
| 执行 SQL | store.executeSql(sql) | void(Promise) |
| 原生查询 | store.querySql(sql, 列投影?) | ResultSet(Promise) |
| 增 | store.insert(table, values) | 新记录自增 id |
| 删 | store.delete(predicates) | 受影响行数 |
| 改 | store.update(values, predicates) | 受影响行数 |
| 查 | store.query(predicates, 列投影?) | ResultSet |
| 事务 | store.beginTransaction() / commit() / rollBack() | void |
| 取值 | resultSet.getLong / getString / getFloat(colIndex) | 对应类型值 |
| 遍历 | resultSet.goToNextRow() | boolean |
| 释放 | resultSet.close() | void |
十、常见坑(本项目踩过的)
ResultSet忘记close()→ 内存泄漏,每次遍历完必须关闭。- 条件构造器默认 AND → 要"或"的关系必须显式
.or(),且.or()放在两个条件之间。 - 列名大小写/下划线必须与建表 SQL 一致 →
created_time写成createdTime会报列不存在。 insert返回的是 id,不是布尔值;update/delete返回受影响行数,0 行不代表出错。ValuesBucket里不要放主键自增列,放id会插入冲突或覆盖自增逻辑。- 所有操作都是异步的,页面里必须
await或.then/.catch,并处理BusinessError。 - 批量写操作要包事务,既保证原子性又显著提升性能。
- 高频查询字段建索引(
CREATE INDEX IF NOT EXISTS),排序/过滤字段收益最大。
更多推荐

所有评论(0)