HarmonyOS ArkTS 实战:RelationalStore 关系型数据库全攻略(增删改查完整示例)
本文基于鸿蒙初始化流程:先通过创建数据库连接,再执行初始化表结构;查询核心ResultSet需手动遍历和关闭,字段类型需与匹配;条件操作替代原生 SQL 条件,简化查询 / 修改 / 删除的条件构造;资源管理:ResultSet 必须关闭,数据库操作完成后按需释放RdbStore实例;最佳实践:封装工具类、使用事务、分页查询、添加索引,提升代码可维护性和性能。掌握的核心用法后,可轻松应对鸿蒙应用中
在 HarmonyOS 应用开发中,当需要存储结构化、有复杂查询需求的数据(如文章列表、用户信息、订单记录等)时,轻量级的 Preferences 键值存储已无法满足需求。鸿蒙官方提供的RelationalStore(关系型数据库)基于 SQLite 实现,支持标准的增删改查(CRUD)、条件查询、事务等能力,是处理结构化数据的最佳选择。本文将以 “文章管理” 为例,从数据库创建、表结构定义到完整的增删改查,手把手教你掌握RelationalStore的核心用法。
一、核心概念速览(新手必懂)
在开始编码前,先理清RelationalStore的核心概念,避免后续代码 “知其然不知其所以然”:
| 概念 | 作用说明 |
|---|---|
| RdbStore | 关系型数据库的核心管理类,负责数据库的创建、连接,以及执行 SQL / 增删改查操作 |
| RdbPredicates | 查询条件构造器,替代原生 SQL 的 WHERE 子句,支持等值、模糊、范围等条件查询 |
| ResultSet | 查询结果集,存储数据库查询返回的数据,需手动遍历和关闭,避免资源泄露 |
| ValuesBucket | 鸿蒙内置的键值对类型,用于封装数据库操作的参数(如新增 / 修改的字段值) |
| SecurityLevel | 数据库安全级别(S1~S4),S1 为最低级别,适用于非敏感数据,默认即可 |
二、实战:文章管理系统(完整数据库示例)
以下是基于RelationalStore实现的 “文章管理” 完整代码,包含数据库创建、表结构定义、新增文章、查询总数、查询列表、修改、删除、删库全流程,可直接复制使用。
完整代码实现
typescript
运行
import { relationalStore, ValuesBucket } from '@kit.ArkData';
import { getContext } from '@kit.ArkUI';
// 定义文章数据结构(继承ValuesBucket适配数据库参数类型)
interface ArticleItem extends ValuesBucket {
id: number | null; // 主键(自增,新增时传null)
title: string; // 文章标题
content: string; // 文章内容
create_time: number; // 创建时间(时间戳)
}
@Entry
@ComponentV2
struct Demo06DataBase {
// 数据库核心实例
store?: relationalStore.RdbStore;
// 表名(统一管理,避免硬编码)
tableName = 'article';
// 本地状态:文章总数
@Local total: number = 0;
// 本地状态:文章列表
@Local list: ArticleItem[] = [];
/**
* 步骤1:创建数据库+初始化表结构
*/
async createStore() {
try {
// 1. 创建/连接数据库
const store = await relationalStore.getRdbStore(getContext(this), {
name: 'interview_tong.db', // 数据库名称(后缀.db可选,系统自动处理)
securityLevel: relationalStore.SecurityLevel.S1 // 安全级别(S1为默认最低级别)
});
// 2. 执行SQL创建表(IF NOT EXISTS避免重复创建)
const createTableSql = `
CREATE TABLE IF NOT EXISTS ${this.tableName} (
id INTEGER PRIMARY KEY AUTOINCREMENT, -- 主键自增
title TEXT NOT NULL, -- 标题(非空)
content TEXT NOT NULL, -- 内容(非空)
create_time INTEGER NOT NULL -- 创建时间(时间戳,整数型)
)
`;
await store.executeSql(createTableSql);
// 3. 保存数据库实例供后续操作
this.store = store;
console.log('数据库创建+表初始化成功');
} catch (error) {
console.error('数据库初始化失败:', error);
}
}
/**
* 页面加载时初始化数据库
*/
aboutToAppear(): void {
this.createStore();
}
build() {
Column() {
// 1. 新增文章
Button('添加文章')
.margin(5)
.onClick(() => {
if (!this.store) return;
// 构造新增数据(id传null,由数据库自增)
const newArticle: ArticleItem = {
id: null,
title: '测试标题' + Math.random().toFixed(4),
content: '我是一篇测试文章' + Math.random().toFixed(4),
create_time: Date.now() // 当前时间戳
};
// 执行新增操作
this.store.insert(this.tableName, newArticle);
});
// 2. 查询文章总条数
Button('查询总条数')
.margin(5)
.onClick(async () => {
if (!this.store) return;
// 创建查询条件(无条件=查询所有)
const predicates = new relationalStore.RdbPredicates(this.tableName);
// 执行查询,获取结果集
const resultSet = await this.store.query(predicates);
// 获取结果集行数(总条数)
this.total = resultSet?.rowCount || 0;
// 关闭结果集,释放资源
resultSet?.close();
});
Text('文章总条数:' + this.total)
.margin(5)
.fontSize(16);
// 3. 查询所有文章列表
Button('查询所有数据')
.margin(5)
.onClick(async () => {
if (!this.store) return;
const predicates = new relationalStore.RdbPredicates(this.tableName);
// 可选:添加条件查询(如id=3)
// predicates.equalTo('id', 3);
const resultSet = await this.store.query(predicates);
const articleList: ArticleItem[] = [];
// 遍历结果集(goToNextRow():移动到下一行,无数据时返回false)
while (resultSet?.goToNextRow()) {
// 通过字段名获取列索引,再获取对应值(需匹配字段类型)
articleList.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
title: resultSet.getString(resultSet.getColumnIndex('title')),
content: resultSet.getString(resultSet.getColumnIndex('content')),
create_time: resultSet.getLong(resultSet.getColumnIndex('create_time'))
});
}
// 必须关闭结果集,否则会导致资源泄露
resultSet?.close();
// 更新本地列表,驱动UI刷新
this.list = articleList;
});
// 展示文章列表
ForEach(this.list, (item: ArticleItem) => {
Row()
.margin(5)
.padding(8)
.backgroundColor('#f5f5f5')
.borderRadius(4) {
Text('ID:' + item.id)
.fontWeight(600)
.marginRight(10);
Text('标题:' + item.title)
.fontSize(14);
}
});
// 4. 修改第一条文章
Button('修改第一条文章')
.margin(5)
.onClick(() => {
if (!this.store || this.list.length === 0) return;
// 获取第一条数据
const firstArticle = this.list[0];
// 修改标题
firstArticle.title = '修改后的标题' + Math.random().toFixed(4);
// 创建条件:匹配对应ID
const predicates = new relationalStore.RdbPredicates(this.tableName);
predicates.equalTo('id', firstArticle.id);
// 执行同步修改操作
this.store.updateSync(firstArticle, predicates);
// 重新查询列表,刷新UI
this.list[0] = firstArticle;
});
// 5. 删除第一条文章
Button('删除第一条文章')
.margin(5)
.backgroundColor('#ff4444')
.fontColor('#ffffff')
.onClick(() => {
if (!this.store || this.list.length === 0) return;
const firstArticle = this.list[0];
const predicates = new relationalStore.RdbPredicates(this.tableName);
predicates.equalTo('id', firstArticle.id);
// 执行同步删除操作
this.store.deleteSync(predicates);
// 移除本地列表第一条,刷新UI
this.list.shift();
});
// 6. 删除整个数据库
Button('删除数据库')
.margin(5)
.backgroundColor('#cc0000')
.fontColor('#ffffff')
.onClick(() => {
// 执行删库操作(谨慎使用!)
relationalStore.deleteRdbStore(getContext(this), {
name: 'interview_tong.db',
securityLevel: relationalStore.SecurityLevel.S1
});
// 清空本地状态
this.store = undefined;
this.list = [];
this.total = 0;
});
}
.height('100%')
.width('100%')
.padding({ top: 40 })
.justifyContent(FlexAlign.Start);
}
}
三、核心代码深度解析
1. 数据库 + 表初始化(createStore)
这是整个数据库操作的基础,分为 “创建数据库连接” 和 “执行建表 SQL” 两步:
typescript
运行
async createStore() {
// 1. 创建/连接数据库
const store = await relationalStore.getRdbStore(getContext(this), {
name: 'interview_tong.db',
securityLevel: relationalStore.SecurityLevel.S1
});
// 2. 建表SQL
await store.executeSql(`CREATE TABLE IF NOT EXISTS ${this.tableName} (...)`);
this.store = store;
}
getRdbStore:异步方法,若指定名称的数据库不存在则自动创建,存在则直接连接;SecurityLevel.S1:最低安全级别,适用于非敏感数据,无需额外权限配置;CREATE TABLE IF NOT EXISTS:避免重复建表导致的异常,是建表的最佳实践;- 表结构设计:
id设为INTEGER PRIMARY KEY AUTOINCREMENT(主键自增),title/content/create_time均设为NOT NULL(非空约束),保证数据完整性。
2. 新增数据(insert)
typescript
运行
this.store.insert(this.tableName, {
id: null,
title: '测试标题' + Math.random(),
content: '测试内容' + Math.random(),
create_time: Date.now()
});
- 主键
id必须传null:因为设置了自增,由数据库自动生成,传具体数值会覆盖自增逻辑; insert方法:异步操作(也可使用insertSync同步),参数为 “表名 + 数据对象”;- 时间戳存储:
create_time用Date.now()存储整数型时间戳,比字符串日期更易排序和查询。
3. 查询操作(核心难点)
RelationalStore 的查询分为 “获取总数” 和 “获取列表” 两类,核心是ResultSet的处理:
(1)查询总条数
typescript
运行
const predicates = new relationalStore.RdbPredicates(this.tableName);
const resultSet = await this.store.query(predicates);
this.total = resultSet?.rowCount || 0;
resultSet?.close(); // 关键:关闭结果集
RdbPredicates:无参数时代表 “查询所有数据”;resultSet.rowCount:直接获取结果集的行数,即总条数;- 必须关闭 ResultSet:否则会导致数据库连接泄露,多次查询后应用卡顿。
(2)查询所有数据并遍历
typescript
运行
const resultSet = await this.store.query(predicates);
const list: ArticleItem[] = [];
while (resultSet?.goToNextRow()) { // 遍历每一行数据
list.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')), // 整数型字段用getLong
title: resultSet.getString(resultSet.getColumnIndex('title')), // 字符串用getString
content: resultSet.getString(resultSet.getColumnIndex('content')),
create_time: resultSet.getLong(resultSet.getColumnIndex('create_time'))
});
}
resultSet?.close(); // 关闭结果集
this.list = list;
goToNextRow():移动到下一行,无数据时返回false,结束遍历;getColumnIndex('字段名'):通过字段名获取列索引,避免硬编码索引值;- 类型匹配:
INTEGER类型用getLong(),TEXT类型用getString(),类型不匹配会抛出异常。
4. 修改数据(updateSync)
typescript
运行
const predicates = new relationalStore.RdbPredicates(this.tableName);
predicates.equalTo('id', firstArticle.id); // 条件:id等于目标值
this.store.updateSync(firstArticle, predicates);
RdbPredicates.equalTo:等值条件,替代 SQL 的WHERE id = xxx;updateSync:同步修改方法,也可使用update异步方法;- 注意:修改后需手动更新本地列表,驱动 UI 刷新。
5. 删除数据(deleteSync)
typescript
运行
predicates.equalTo('id', firstArticle.id);
this.store.deleteSync(predicates);
deleteSync:根据条件删除数据,无条件时会删除整张表数据;- 本地列表同步:删除数据库数据后,需手动移除本地列表对应项,保证 UI 与数据一致。
6. 删除数据库(deleteRdbStore)
typescript
运行
relationalStore.deleteRdbStore(getContext(this), {
name: 'interview_tong.db',
securityLevel: relationalStore.SecurityLevel.S1
});
- 删库操作会删除数据库文件及所有表数据,谨慎使用;
- 删库后需清空
store实例和本地状态,避免后续操作报错。
四、关键避坑指南
1. ResultSet 未关闭导致资源泄露
- 现象:多次查询后应用卡顿、数据库操作超时;
- 解决方案:所有查询操作完成后,必须调用
resultSet.close()释放资源。
2. 主键自增传值错误
- 错误示例:新增时
id传0或undefined; - 解决方案:新增时
id必须传null,让数据库自动生成自增 ID。
3. 数据类型不匹配
- 错误示例:用
getString()获取create_time(整数型); - 解决方案:严格匹配字段类型:
INTEGER→getLong()TEXT→getString()REAL→getDouble()
4. 异步操作未等待
- 错误示例:
createStore未执行完就调用insert,导致store为undefined; - 解决方案:数据库初始化放在
aboutToAppear(异步执行),或在操作前判断store是否存在:typescript
运行
if (!this.store) { promptAction.showToast({ message: '数据库未初始化' }); return; }
5. 表名 / 字段名硬编码
- 风险:后期修改表名 / 字段名时,需修改所有相关代码,易漏改;
- 解决方案:用常量统一管理表名和字段名:
typescript
运行
const TABLE_NAME = 'article'; const FIELD_ID = 'id'; const FIELD_TITLE = 'title';
五、扩展优化建议
1. 封装数据库工具类(核心优化)
将重复的数据库操作封装为工具类,避免页面代码冗余,示例:
typescript
运行
// utils/DbUtil.ets
import { relationalStore, ValuesBucket } from '@kit.ArkData';
import { getContext } from '@kit.ArkUI';
export class DbUtil {
private static store: relationalStore.RdbStore | null = null;
private static DB_NAME = 'interview_tong.db';
// 初始化数据库
public static async init() {
if (!this.store) {
this.store = await relationalStore.getRdbStore(getContext(globalThis), {
name: this.DB_NAME,
securityLevel: relationalStore.SecurityLevel.S1
});
// 建表SQL
await this.store.executeSql(`CREATE TABLE IF NOT EXISTS article (...)`);
}
return this.store;
}
// 新增数据
public static async insert(tableName: string, data: ValuesBucket) {
const store = await this.init();
return store.insert(tableName, data);
}
// 条件查询
public static async query(tableName: string, predicates: relationalStore.RdbPredicates) {
const store = await this.init();
const resultSet = await store.query(predicates);
const list: any[] = [];
while (resultSet.goToNextRow()) {
// 通用遍历逻辑(可扩展)
}
resultSet.close();
return list;
}
}
2. 分页查询(避免大数据量卡顿)
当表中数据量较大时,全量查询会导致 UI 卡顿,需实现分页:
typescript
运行
// 分页查询示例(每页10条,查第2页)
const predicates = new relationalStore.RdbPredicates(this.tableName);
predicates.limit(10, 10); // limit(每页条数, 偏移量)
const resultSet = await this.store.query(predicates);
3. 添加事务处理(保证数据一致性)
批量新增 / 修改时,使用事务避免部分操作失败导致数据不一致:
typescript
运行
async batchInsert(articles: ArticleItem[]) {
const store = await this.init();
// 开启事务
store.beginTransaction();
try {
articles.forEach(article => store.insert(this.tableName, article));
// 提交事务
store.commitTransaction();
} catch (error) {
// 回滚事务
store.rollbackTransaction();
console.error('批量新增失败:', error);
}
}
4. 添加索引(优化查询速度)
对高频查询的字段(如create_time)添加索引,提升查询效率:
typescript
运行
// 新增索引SQL
await store.executeSql(`CREATE INDEX IF NOT EXISTS idx_article_create_time ON ${this.tableName}(create_time)`);
六、总结
本文基于鸿蒙RelationalStore实现了完整的关系型数据库增删改查示例,核心要点可总结为:
- 初始化流程:先通过
getRdbStore创建数据库连接,再执行CREATE TABLE初始化表结构; - 查询核心:
ResultSet需手动遍历和关闭,字段类型需与getLong/getString匹配; - 条件操作:
RdbPredicates替代原生 SQL 条件,简化查询 / 修改 / 删除的条件构造; - 资源管理:ResultSet 必须关闭,数据库操作完成后按需释放
RdbStore实例; - 最佳实践:封装工具类、使用事务、分页查询、添加索引,提升代码可维护性和性能。
掌握RelationalStore的核心用法后,可轻松应对鸿蒙应用中结构化数据的存储需求,无论是文章管理、用户中心还是订单系统,都能基于这套逻辑快速实现。
更多推荐

所有评论(0)