鸿蒙原生ArkTS-SQLite数据库存储用户信息
本文介绍了在HarmonyOS中使用ArkTS语言和relationalStore API实现本地SQLite数据库的完整开发流程。主要内容包括: HarmonyOS数据存储方案对比,重点介绍关系型数据库relationalStore的优势 开发环境配置要求及项目结构说明 relationalStore核心API详解,包括数据库配置、CRUD操作和查询构建等 用户信息管理模块的数据库设计,包含表结
虚拟机上运行效果,在本地的预览器中是无法体现的。

一、引言
在移动应用开发中,本地数据持久化是一个永恒的核心需求。无论是用户信息缓存、离线数据浏览,还是复杂查询分析,本地数据库都扮演着不可或缺的角色。HarmonyOS 作为华为自主研发的全场景分布式操作系统,为开发者提供了强大而简洁的本地数据存储解决方案——@ohos.data.relationalStore(关系型数据库 API)。
本文将基于一个实战项目,深入讲解如何在鸿蒙原生应用中,使用 ArkTS 语言和 relationalStore API,构建一个完整的用户信息管理模块。该项目实现了 SQLite 数据库的创建、表结构定义、增删改查(CRUD)、模糊搜索、批量数据插入等功能,并配合精美的 UI 交互界面,是一篇理论与实践并重的技术文章。
文章将从环境搭建、API 设计、代码实现、UI 开发、性能优化等维度展开,力求让读者不仅掌握 SQLite 在 HarmonyOS 中的使用方法,更能理解其背后的设计思想和最佳实践。
项目源代码基于 HarmonyOS API 12 + ArkTS,DevEco Studio 5.0 环境编译通过,可直接运行验证。
二、HarmonyOS 数据存储方案概述
2.1 本地存储技术矩阵
HarmonyOS 为开发者提供了丰富的数据持久化方案,按使用场景可分为以下几类:
| 存储方案 | 适用场景 | 数据格式 | 容量上限 | 特点 |
|---|---|---|---|---|
| Preferences | 轻量级键值对 | 键-值 | 少量(< 10 KB) | 同步读写、内存缓存 |
| UserFileManager | 用户文件管理 | 二进制文件 | 大文件 | 跨应用文件共享 |
| relationalStore(SQLite) | 结构化关系数据 | 表 + SQL | 无上限 | 原子事务、谓词查询 |
| KV Store(分布式) | 分布式设备数据同步 | 键-值 | 受设备限制 | 跨设备自动同步 |
其中,relationalStore 是 HarmonyOS 原生提供的 SQLite 关系型数据库接口,本文的主角就是它。
2.2 为什么选择 relationalStore / SQLite?
- 零配置:无需额外安装数据库引擎,HarmonyOS 系统自带 SQLite 支持。
- 关系型模型:天然支持表的关联查询、排序、分组等标准数据库操作。
- 事务安全:支持 ACID(原子性、一致性、隔离性、持久性)事务特性。
- 异步 API:全部操作基于 Promise / async-await,不阻塞 UI 线程。
- 锁机制:内置读写锁,支持多线程并发访问。
- 查询灵活:提供谓词(RdbPredicates)查询方式,拼装查询条件直观且安全,有效防止 SQL 注入。
三、项目结构与运行环境
3.1 项目根目录结构
Demo0528/
├── entry/
│ └── src/main/ets/pages/
│ ├── Index.ets ← SQLite 数据库操作首页
│ ├── SqliteDemo.ets ← 同 Index.ets(备份副本)
│ ├── Index_Origin.ets ← 原 AI 问答首页(备份)
│ ├── AIChatService.ets ← AI 聊天服务封装
│ ├── LoginPage.ets ← 登录页面示例
│ └── ColumnStartLayout.ets
├── AppScope/
│ └── app.json5 ← 应用全局配置
├── build-profile.json5 ← 工程级构建配置
├── hvigor/
│ └── hvigor-config.json5 ← Hvigor 构建工具配置
├── oh-package.json5 ← 全局包依赖管理
└── 鸿蒙ArkTS-300个AI对话应用大全.xlsx
3.2 开发环境要求
| 组件 | 版本要求 |
|---|---|
| DevEco Studio | 5.0+ |
| HarmonyOS SDK | API 12+ |
| ArkTS 版本 | 5.0+ |
| Node.js | 18+(Hvigor 构建引擎依赖) |
3.3 模块级配置
在 entry/build-profile.json5 中需确保:
{
"apiType": "stageMode",
"buildOption": {
"arkOptions": {
"intelliJ": { ... }
}
}
}
在 entry/src/main/module.json5 中,应用需要声明使用关系型数据库的权限(API 12 默认已开放,无需额外声明敏感权限):
{
module: {
// ...
requestPermissions: []
// relationalStore 属于 normal-level API,无需权限声明
}
}
四、relationalStore API 核心概念详解
4.1 关键模块导入
import relationalStore from '@ohos.data.relationalStore';
import { BusinessError } from '@ohos.base';
relationalStore:核心模块,提供数据库的创建、打开、删除等操作。BusinessError:HarmonyOS 统一的错误类型定义,用于捕获和处理异步操作的异常。
4.2 核心类型与接口
| 类型/接口 | 说明 | 关键成员 |
|---|---|---|
StoreConfig |
数据库配置对象 | name(数据库名)、securityLevel(安全等级) |
RdbStore |
数据库实例,代表一个打开的数据库 | insert()、delete()、update()、query()、executeSql() |
RdbPredicates |
查询谓词,用于构建 WHERE 条件 | equalTo()、like()、orderByAsc()、in()、beginsWith() |
ResultSet |
查询结果集(游标) | goToNextRow()、getLong()、getString()、close() |
ValuesBucket |
用于插入/更新的数据容器 | 键值对形式,键为列名,值为对应的数据 |
SecurityLevel |
数据库安全等级 | S1(低)、S2(中)、S3(高) |
4.3 安全等级说明
SecurityLevel.S1 // 最低安全等级,适合公开数据
SecurityLevel.S2 // 中等安全等级,适合隐私数据
SecurityLevel.S3 // 最高安全等级,适合敏感数据
对于用户信息管理,推荐使用 S1 或 S2 级别。
五、数据库设计与初始化
5.1 数据模型定义
我们的用户信息表包含以下字段:
interface UserItem {
id: number; // 主键,自增
name: string; // 用户名(不可为空)
age: number; // 年龄
city: string; // 所在城市
createTime: string; // 创建时间
}
对应的建表 SQL:
const DB_NAME: string = 'demo.db';
const TABLE_NAME: string = 'user';
const CREATE_SQL: string = `CREATE TABLE IF NOT EXISTS ${TABLE_NAME} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER DEFAULT 0,
city TEXT DEFAULT '',
createTime TEXT DEFAULT ''
)`;
设计要点:
id使用INTEGER PRIMARY KEY AUTOINCREMENT,确保每条记录有唯一自增 ID。name设置NOT NULL,保证数据的完整性。age和city设置DEFAULT默认值,避免插入时字段缺失导致错误。createTime使用TEXT类型存储格式化的日期时间字符串,而不是使用 SQLite 的内置时间函数,这样前端显示更灵活。
5.2 数据库初始化流程
aboutToAppear(): void {
this.initDatabase();
}
async initDatabase(): Promise<void> {
this.dbStatus = '正在连接...';
try {
const config: relationalStore.StoreConfig = {
name: DB_NAME,
securityLevel: relationalStore.SecurityLevel.S1,
};
this.rdbStore = await relationalStore.getRdbStore(getContext(this), config);
await this.rdbStore.executeSql(CREATE_SQL);
this.dbStatus = '已连接 (demo.db)';
this.showMsg('数据库初始化成功', 'success');
await this.loadAllUsers();
} catch (e) {
const err = e as BusinessError;
this.dbStatus = '连接失败';
this.showMsg(`数据库错误: ${err.message}`, 'error');
}
}
初始化步骤:
- 构建 StoreConfig:指定数据库名称和安全等级。
- 调用 getRdbStore():如果数据库不存在则自动创建,存在则直接打开。该方法返回
RdbStore实例。 - 执行建表 SQL:使用
executeSql()执行CREATE TABLE IF NOT EXISTS,确保表已创建。 - 加载已有数据:初始化成功后立即查询所有用户并展示。
- 错误处理:捕获
BusinessError类型异常,向用户展示友好的错误信息。
关键 API 说明:
getRdbStore(context: Context, config: StoreConfig): Promise<RdbStore>
context:通过getContext(this)获取组件的上下文对象。config:数据库配置,包括名称和安全等级。- 返回值:一个
Promise<RdbStore>,即数据库操作的入口对象。
executeSql(sql: string, bindArgs?: ValueType[]): Promise<void>
- 用于执行任意 SQL 语句(DDL / DML),支持参数化绑定。
- 返回
void,适合建表、索引创建等操作。
六、CRUD 操作全面解析
6.1 插入数据(Create)
单条插入
async insertUser(): Promise<void> {
if (!this.rdbStore) return;
const name = this.inputName.trim();
if (!name) {
this.showMsg('请输入姓名', 'error');
return;
}
const age = parseInt(this.inputAge) || 0;
const city = this.inputCity.trim() || '未知';
const now = new Date();
const timeStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, '0')}-${String(now.getDate()).padStart(2, '0')} ${String(now.getHours()).padStart(2, '0')}:${String(now.getMinutes()).padStart(2, '0')}`;
try {
const row: relationalStore.ValuesBucket = {
name: name,
age: age,
city: city,
createTime: timeStr,
};
await this.rdbStore.insert(TABLE_NAME, row);
this.showMsg(`✅ 已添加: ${name}`, 'success');
this.inputName = '';
this.inputAge = '';
this.inputCity = '';
await this.loadAllUsers();
} catch (e) {
this.showMsg(`插入失败: ${(e as BusinessError).message}`, 'error');
}
}
插入操作要点:
- 数据验证:插入前检查 name 不能为空。
- 默认值处理:年龄为空时设为 0,城市为空时设为
'未知'。 - 时间格式化:使用
padStart()补零,生成"2025-05-28 16:30"格式的时间字符串。 - ValuesBucket 接口:键为列名,值为对应数据。注意不要包含自增主键
id。 - 插入后刷新列表:调用
loadAllUsers()重新加载用户列表。
API 签名:
rdbStore.insert(table: string, values: ValuesBucket): Promise<number>
返回值为新插入行的 rowId(Promise<number>)。
批量插入(测试数据)
async insertMockData(): Promise<void> {
const names = ['张三', '李四', '王五', '赵六', '孙七', '周八', '吴九', '郑十',
'Alice', 'Bob', 'Charlie', 'Diana', 'Eve', 'Frank'];
const cities = ['北京', '上海', '深圳', '杭州', '成都', '广州', '武汉', '南京'];
if (!this.rdbStore) return;
try {
for (const n of names) {
const row: relationalStore.ValuesBucket = {
name: n,
age: Math.floor(Math.random() * 40) + 18,
city: cities[Math.floor(Math.random() * cities.length)],
createTime: new Date().toISOString().slice(0, 19).replace('T', ' '),
};
await this.rdbStore.insert(TABLE_NAME, row);
}
this.showMsg(`✅ 已插入 ${names.length} 条测试数据`, 'success');
await this.loadAllUsers();
} catch (e) {
this.showMsg(`插入失败: ${(e as BusinessError).message}`, 'error');
}
}
批量插入支持中文和英文两种类型的测试数据,配合随机年龄和城市,方便快速验证查询和搜索功能。
性能优化建议:当批量插入数据量较大时(> 100 条),建议使用事务包裹,将多次 insert 放在一个事务中提交,可显著提升写入性能。示例:
await this.rdbStore.beginTransaction(); try { for (...) { await this.rdbStore.insert(...); } await this.rdbStore.commit(); } catch { await this.rdbStore.rollback(); }
6.2 查询数据(Read)
查询全部(按 ID 升序)
async loadAllUsers(): Promise<void> {
if (!this.rdbStore) return;
try {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.orderByAsc('id');
const resultSet = await this.rdbStore.query(
predicates,
['id', 'name', 'age', 'city', 'createTime']
);
const list: UserItem[] = [];
while (resultSet.goToNextRow()) {
list.push({
id: resultSet.getLong(0),
name: resultSet.getString(1),
age: resultSet.getLong(2),
city: resultSet.getString(3),
createTime: resultSet.getString(4),
});
}
resultSet.close();
this.users = list;
} catch (e) {
this.showMsg(`查询失败: ${(e as BusinessError).message}`, 'error');
}
}
查询流程:
- 创建 RdbPredicates:指定要查询的表名。
- 设置排序:
orderByAsc('id')表示按 ID 升序排列。还有orderByDesc()方法。 - 执行查询:
query(predicates, columns)返回ResultSet结果集。 - 遍历结果集:使用
goToNextRow()逐行移动游标。 - 读取列值:通过列索引读取,
getLong(0)读第 0 列(id),getString(1)读第 1 列(name),以此类推。 - 关闭结果集:必须调用
resultSet.close()释放资源,否则可能导致内存泄漏。
API 签名:
query(predicates: RdbPredicates, columns: Array<string>): Promise<ResultSet>
模糊搜索
async searchUser(): Promise<void> {
if (!this.rdbStore) return;
const keyword = this.inputName.trim();
try {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
if (keyword) {
predicates.like('name', `%${keyword}%`);
}
predicates.orderByAsc('id');
const resultSet = await this.rdbStore.query(
predicates,
['id', 'name', 'age', 'city', 'createTime']
);
const list: UserItem[] = [];
while (resultSet.goToNextRow()) {
list.push({
id: resultSet.getLong(0),
name: resultSet.getString(1),
age: resultSet.getLong(2),
city: resultSet.getString(3),
createTime: resultSet.getString(4),
});
}
resultSet.close();
this.users = list;
this.showMsg(`🔍 搜索完成,共 ${list.length} 条结果`, list.length > 0 ? 'success' : 'info');
} catch (e) {
this.showMsg(`搜索失败: ${(e as BusinessError).message}`, 'error');
}
}
模糊搜索要点:
- 使用
predicates.like('name', '%keyword%')构建WHERE name LIKE '%keyword%'的条件。 %是 SQL 通配符,表示任意字符序列。- 搜索关键字为空时,不添加
like条件,相当于查询全部。 - 安全性:RdbPredicates 使用参数化查询,自动对输入值进行转义,有效防止 SQL 注入攻击。
RdbPredicates 常用方法一览:
| 方法 | 说明 | 示例 |
|---|---|---|
equalTo(field, value) |
等于 | equalTo('city', '北京') |
notEqualTo(field, value) |
不等于 | notEqualTo('age', 0) |
greaterThan(field, value) |
大于 | greaterThan('age', 18) |
lessThan(field, value) |
小于 | lessThan('age', 60) |
like(field, pattern) |
模糊匹配 | like('name', '%张%') |
beginsWith(field, value) |
前缀匹配 | beginsWith('name', '张') |
in(field, values) |
在集合中 | in('city', ['北京','上海']) |
orderByAsc(field) |
升序排序 | orderByAsc('id') |
orderByDesc(field) |
降序排序 | orderByDesc('createTime') |
limit(count) |
限制返回条数 | limit(10) |
6.3 删除数据(Delete)
删除单条记录
async deleteUser(id: number): Promise<void> {
if (!this.rdbStore) return;
try {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('id', id);
await this.rdbStore.delete(predicates);
this.showMsg(`已删除 id=${id}`, 'info');
await this.loadAllUsers();
} catch (e) {
this.showMsg(`删除失败: ${(e as BusinessError).message}`, 'error');
}
}
API 签名:
delete(predicates: RdbPredicates): Promise<number>
返回被删除的行数。
清空全部数据
async clearAll(): Promise<void> {
if (!this.rdbStore) return;
try {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
await this.rdbStore.delete(predicates);
this.showMsg('已清空所有数据', 'info');
await this.loadAllUsers();
} catch (e) {
this.showMsg(`清空失败: ${(e as BusinessError).message}`, 'error');
}
}
清空技巧:不添加任何条件的 delete(predicates) 相当于执行 DELETE FROM table,会删除表中所有数据。这与 DROP TABLE 不同——表结构仍然保留,下次直接插入即可。
6.4 更新数据(Update)
虽然当前示例 UI 没有提供编辑功能(留作读者扩展练习),但 update 操作的 API 同样简洁:
async updateUserAge(id: number, newAge: number): Promise<void> {
if (!this.rdbStore) return;
try {
const predicates = new relationalStore.RdbPredicates(TABLE_NAME);
predicates.equalTo('id', id);
const values: relationalStore.ValuesBucket = { age: newAge };
await this.rdbStore.update(values, predicates);
await this.loadAllUsers();
} catch (e) {
this.showMsg(`更新失败: ${(e as BusinessError).message}`, 'error');
}
}
API 签名:
update(values: ValuesBucket, predicates: RdbPredicates): Promise<number>
返回被更新的行数。
七、UI 界面设计与 ArkTS 组件实践
7.1 整体布局结构
页面采用经典的单列垂直布局,从上到下分为六个区域:
┌─────────────────────────────────────────┐
│ 顶部标题栏 (紫色渐变) │ ← Row + Text
├─────────────────────────────────────────┤
│ 消息提示条 (成功/错误/信息 三种颜色) │ ← Text(条件渲染)
├─────────────────────────────────────────┤
│ 新增用户表单 (姓名/年龄/城市/操作按钮) │ ← Column + Row + TextInput + Button
├─────────────────────────────────────────┤
│ 搜索栏 (输入框 + 搜索/全部按钮) │ ← Row + TextInput + Button × 2
├─────────────────────────────────────────┤
│ 用户列表 (List + ListItem + 头像/姓名/...)│ ← List + ForEach
├─────────────────────────────────────────┤
│ 底部提示文字 │ ← Text
└─────────────────────────────────────────┘
7.2 状态管理设计
页面使用 @State 装饰器管理响应式状态:
@State dbStatus: string = '未连接';
@State users: UserItem[] = [];
@State inputName: string = '';
@State inputAge: string = '';
@State inputCity: string = '';
@State msg: string = '';
@State msgType: 'success' | 'error' | 'info' = 'info';
dbStatus:数据库连接状态展示在标题栏右侧。users:用户列表数据,UI 自动响应更新。inputName/inputAge/inputCity:表单输入框双向绑定。msg/msgType:操作结果提示消息及其类型。
7.3 关键 UI 组件详解
消息提示组件
采用条件渲染(if 指令),当 this.msg 非空时显示提示条:
if (this.msg) {
Text(this.msg)
.width('100%')
.padding(12)
.fontSize(14)
.fontColor('#FFFFFF')
.backgroundColor(
this.msgType === 'success' ? '#52C41A' :
this.msgType === 'error' ? '#F5222D' : '#1890FF'
)
.borderRadius(8)
.margin({ bottom: 12 })
}
输入表单组件
使用 TextInput 获取用户输入,Button 触发操作:
TextInput({ placeholder: '姓名 *', text: this.inputName })
.height(42).fontSize(15)
.onChange(v => this.inputName = v)
// 年龄和城市并列一行
Row() {
TextInput({ placeholder: '年龄', text: this.inputAge })
.layoutWeight(1).height(42).fontSize(15)
.type(InputType.Number) // 限制数字键盘
.onChange(v => this.inputAge = v)
TextInput({ placeholder: '城市', text: this.inputCity })
.layoutWeight(1).height(42).fontSize(15).margin({ left: 8 })
.onChange(v => this.inputCity = v)
}
用户列表组件
使用 List + ForEach 构建高性能滚动列表:
List({ space: 8 }) {
ForEach(this.users, (item: UserItem) => {
ListItem() {
Row() {
// 圆形头像(首字母)
Stack() {
Circle().width(40).height(40).fill('#5B6ABF')
Text(item.name.slice(0, 1))
.fontSize(18).fontColor('#FFFFFF').fontWeight(FontWeight.Bold)
}
.width(40).height(40).margin({ right: 12 })
// 用户信息
Column() {
Row() {
Text(item.name).fontSize(16).fontWeight(FontWeight.Medium)
Text(`${item.age} 岁`).fontSize(13).fontColor('#888').margin({ left: 10 })
Text(`📍 ${item.city}`).fontSize(13).fontColor('#888').margin({ left: 10 })
}
Text(`🕐 ${item.createTime}`).fontSize(12).fontColor('#AAA').margin({ top: 2 })
}
.layoutWeight(1).alignItems(HorizontalAlign.Start)
// 删除按钮
Button('✕')
.width(32).height(32)
.backgroundColor('rgba(245,34,45,0.1)')
.fontColor('#F5222D').fontSize(16)
.borderRadius(16)
.onClick(() => this.deleteUser(item.id))
}
.width('100%').padding(12)
.backgroundColor('#FFFFFF').borderRadius(10)
}
}, (item: UserItem) => String(item.id))
}
列表设计亮点:
- Stack 实现圆形头像:使用
Circle+Text组合,显示用户姓名的首字母作为头像,比图片头像更轻量。 - Column + Row 布局用户信息:姓名、年龄、城市在同一行,创建时间在下一行,信息层级清晰。
- 右侧删除按钮:使用半透明红色背景的 ✕ 按钮,鼠标悬停或触控点击即可删除,交互直观。
- ListItem key 绑定:使用
String(item.id)作为唯一 key,确保列表高性能 diff 更新。
八、完整代码逐段精讲
8.1 变量声明与装饰器
@Entry
@Component
struct SqliteDemo {
@State dbStatus: string = '未连接';
@State users: UserItem[] = [];
// ... 其他 @State 变量
private rdbStore?: relationalStore.RdbStore;
@Entry:标记该组件为页面入口,支持路由跳转。@Component:声明这是一个可复用的自定义组件。@State:标记的变量是响应式的,当其值变化时,UI 会自动重新渲染。private rdbStore?:可选类型的私有成员变量,用于持有 RdbStore 实例。
8.2 生命周期钩子
aboutToAppear(): void {
this.initDatabase();
}
aboutToAppear 是 ArkTS 组件的生命周期函数,在组件即将显示时调用。最适合在此时初始化数据库并加载数据。
8.3 数据库操作层
所有的数据库操作都被封装为 async 异步方法:
| 方法 | 功能 | 调用的关系型 API |
|---|---|---|
initDatabase() |
初始化数据库和表 | getRdbStore()、executeSql() |
loadAllUsers() |
查询全部用户 | query() |
insertUser() |
插入单个用户 | insert() |
deleteUser(id) |
按 ID 删除用户 | delete() |
insertMockData() |
批量插入测试数据 | insert() |
clearAll() |
清空全部数据 | delete() |
searchUser() |
模糊搜索用户 | query() + like() |
这种"数据方法分离"的设计模式使得代码结构清晰,业务逻辑与 UI 渲染解耦,易于维护和扩展。
8.4 辅助函数
showMsg(text: string, type: 'success' | 'error' | 'info'): void {
this.msg = text;
this.msgType = type;
}
集中管理消息状态的方法,通过联合类型限制 type 只能取三种值,配合 UI 中的三元表达式渲染不同颜色的提示条。
九、错误处理与边界情况
9.1 异常捕获模式
项目中统一使用 try-catch + BusinessError 处理异步操作异常:
try {
// 数据库操作...
} catch (e) {
const err = e as BusinessError;
this.showMsg(`操作失败: ${err.message}`, 'error');
}
9.2 关键边界情况处理
| 场景 | 处理方式 |
|---|---|
| 数据库打开失败 | 设置 dbStatus = '连接失败',显示错误提示 |
| 插入姓名为空 | 前端校验拦截,提示"请输入姓名" |
| 年龄输入非数字 | parseInt() 转换,失败时默认 0 |
| 城市为空 | 默认赋值 '未知' |
| 搜索无结果 | 显示结果数为 0,友好提示信息 |
| 删除最后一条数据 | 列表自动刷新,显示"暂无数据"的占位信息 |
| 未初始化时调用操作 | if (!this.rdbStore) return; 安全保护 |
9.3 RdbStore 是否为空的保护
在所有数据库操作方法的第一行,都有:
if (!this.rdbStore) return;
这是因为 rdbStore 是在 aboutToAppear 中异步初始化的。如果在初始化完成前用户点击了操作按钮,这个防御性判断可以避免 Cannot read properties of undefined 的运行时错误。
十、ResultSet 使用最佳实践
10.1 ResultSet 是一个游标
ResultSet 类似于 Java 的 JDBC ResultSet 或 Android 的 Cursor。它不会一次性把所有数据加载到内存,而是通过游标逐行读取。这在查询大量数据时非常高效。
10.2 正确遍历模式
const list: UserItem[] = [];
while (resultSet.goToNextRow()) {
list.push({
id: resultSet.getLong(0),
name: resultSet.getString(1),
age: resultSet.getLong(2),
city: resultSet.getString(3),
createTime: resultSet.getString(4),
});
}
resultSet.close();
注意事项:
goToNextRow()初始位置在第一条记录之前,第一次调用后指向第一条。- 列索引从 0 开始,与
query()中columns参数的顺序一致。 - 每一列的类型必须与
getXxx()方法匹配——整型用getLong(),文本用getString()。 - 务必调用
close()释放资源——ResultSet 持有数据库连接资源,不关闭可能导致后续操作失败。
10.3 ResultSet 其他有用方法
| 方法 | 说明 |
|---|---|
rowCount |
查询结果总行数 |
columnCount |
结果集的列数 |
isAtLastRow |
是否在最后一行 |
getColumnIndex(columnName) |
根据列名获取索引 |
goToFirstRow() |
跳到第一行 |
goToLastRow() |
跳到最后一行 |
十一、从数据库模型到 UI 渲染的数据流
理解数据在 ArkTS 中的流转过程,是掌握鸿蒙应用开发的关键。
用户操作(点击按钮)
│
▼
事件回调(onClick)
│
▼
调用 async 方法(insertUser / searchUser / deleteUser...)
│
▼
relationalStore API(insert / query / delete...)
│
▼
SQLite 引擎(执行 SQL)
│
▼
返回结果 / 触发回调
│
▼
更新 @State 变量(this.users = list / this.msg = 'xxx')
│
▼
ArkUI 渲染引擎自动更新 UI
核心要点:数据修改必须通过响应式变量(@State),ArkUI 框架才能自动触发节点树的重新渲染。直接修改普通变量(非 @State)不会更新 UI。
十二、项目运行与测试
12.1 运行步骤
- 使用 DevEco Studio 打开项目根目录
Demo0528。 - 等待 Gradle 同步完成(或 Hvigor 自动配置)。
- 连接真机或启动模拟器(建议使用 API 12 及以上版本)。
- 点击运行按钮(▶️)或执行命令:
hvigorw assembleHap --no-daemon
- 安装 HAP 包到设备,应用自动启动。
12.2 功能测试清单
| 测试项 | 操作 | 预期结果 |
|---|---|---|
| 数据库初始化 | 启动应用 | 标题栏显示"已连接 (demo.db)“,提示"数据库初始化成功” |
| 添加用户 | 输入姓名"测试用户"、年龄25、城市"广州",点击添加 | 列表新增一条记录,输入框清空 |
| 批量插入 | 点击"批量测试" | 插入14条随机数据(中英文混合) |
| 模糊搜索 | 搜索框输入"张",点击搜索 | 只显示姓名包含"张"的用户 |
| 搜索全部 | 清空搜索框,点击"全部" | 恢复显示所有用户 |
| 删除单条 | 点击某用户右侧 ✕ 按钮 | 该用户被删除,列表刷新 |
| 清空全部 | 点击"清空"按钮 | 所有用户被删除,显示"暂无数据"占位 |
12.3 调试技巧
在数据库操作中插入日志输出,方便调试:
console.info(`[SQLite] query result: ${list.length} rows`);
HarmonyOS 的日志输出可在 DevEco Studio 的 Log 窗口查看,也可使用 hilog 命令:
hilog -D -r
十三、扩展与增强方向
本文的项目实现了一个功能完整的 SQLite 数据库管理示例,但作为一个真实应用中的用户管理模块,还有许多可以增强的方向:
13.1 功能扩展
- 用户编辑功能:增加编辑按钮,弹出对话框修改用户信息,使用
rdbStore.update()提交。 - 分页加载:使用
predicates.limit(offset, count)实现分页查询,配合滚动加载提升大数据量性能。 - 数据导出:将用户列表导出为 CSV 或 Excel 文件,使用
@ohos.file.fsAPI 写入文件。 - 数据统计:使用 SQL 聚合函数(COUNT / AVG / GROUP BY)统计各城市用户数、平均年龄等。
- 多表关联:增加订单表、地址表,学习外键和联表查询。
13.2 UI 增强
- SwipeToDelete:使用
SwipeAction组件实现左滑删除交互。 - 下拉刷新:使用
PullToRefresh组件(或自定义Scroll刷新逻辑)。 - 排序切换:增加按年龄、按时间升降序切换的按钮。
- 空状态插图:暂无数据时显示 SVG 插画,替代简单的文字提示。
- 夜间模式:使用
@Styles和@Extend构建主题切换系统。
13.3 性能优化
- 事务批量插入:100+ 条数据时开启事务,耗时缩短 10 倍以上。
- 索引优化:为经常搜索的
name列创建索引。 - ResultSet 关闭:使用
finally或在try的结尾确保close()调用。 - 列表懒加载:使用
LazyForEach替代ForEach处理超长列表。
十四、常见问题解答(FAQ)
Q1:数据库文件存储在设备的什么位置?
A:每个应用的数据存储在独立的沙盒目录中。数据库文件位于:
/data/app/el2/100/database/<包名>/entry/rdb/demo.db
普通用户无法直接访问(需要 root 权限),卸载应用时数据库文件会自动被删除。
Q2:如何查看数据库内容?
A:可以使用以下方式:
- DevEco Studio 的 App Inspection 工具(推荐)。
- hdc 命令行:
hdc shell进入设备,sqlite3 <db_path>打开数据库。 - 导出到本地:
hdc file recv <remote_path> <local_path>。
Q3:getRdbStore 和 executeSql 的区别?
A:getRdbStore 用于打开或创建数据库,返回 RdbStore 实例;executeSql 是 RdbStore 实例的方法,用于执行任意 SQL 语句。必须先调用 getRdbStore 获取实例,才能执行 SQL。
Q4:数据库安全等级 S1/S2/S3 有什么区别?
A:安全等级控制数据库文件的加密强度:
- S1:最低,不加密,适合公开数据。
- S2:中等,文件级加密,适合个人隐私数据。
- S3:最高,全量加密,适合密钥等敏感数据。
选择等级需要权衡性能和安全。如果存储的是公开的用户演示数据,S1 即可。
Q5:如何处理数据库版本升级?
A:getRdbStore 支持传入 StoreConfig.version 参数。当版本号增加时,系统会触发 onUpgrade 回调,开发者可以在此执行 ALTER TABLE 等迁移 SQL。但本文为了保持示例简洁,未使用版本管理。
十五、总结
本文从 HarmonyOS 的 relationalStore API 出发,通过构建一个完整的用户信息管理页面,系统地讲解了:
- 如何在鸿蒙应用中集成 SQLite 数据库。
- 如何使用
RdbPredicates构建灵活的查询条件。 - 如何实现完整的 CRUD(增、删、改、查)功能。
- 如何使用 ArkTS 的
@State响应式编程模型驱动 UI 刷新。 - 如何使用
List、TextInput、Button等基础组件构建交互界面。 - 如何处理异步操作中的错误和边界情况。
- 如何编写高效、安全的数据库访问代码。
HarmonyOS 的 relationalStore API 设计成熟而精简,既有 SQLite 的强大能力,又有现代的 async/await 异步编程模型,加上 ArkTS 的响应式 UI 框架,让本地数据存储的开发体验非常流畅。
无论是开发一个简单的通讯录、笔记应用,还是构建复杂的离线数据分析系统,本文所介绍的知识都可以直接迁移应用。希望这篇实战指南能够帮助你在鸿蒙生态的开发之路上更进一步。
十六、参考资料
- HarmonyOS 开发者文档 - relationalStore
- HarmonyOS 开发者文档 - ArkTS 编程规范
- HarmonyOS 开发者文档 - 数据管理
- SQLite 官方文档
- DevEco Studio 下载与安装
本文由 AtomCode 生成,项目源代码可在 HarmonyOS 真机或模拟器上直接运行验证。
更多推荐

所有评论(0)