虚拟机上运行效果,在本地的预览器中是无法体现的。
在这里插入图片描述
在这里插入图片描述

一、引言

在移动应用开发中,本地数据持久化是一个永恒的核心需求。无论是用户信息缓存、离线数据浏览,还是复杂查询分析,本地数据库都扮演着不可或缺的角色。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?

  1. 零配置:无需额外安装数据库引擎,HarmonyOS 系统自带 SQLite 支持。
  2. 关系型模型:天然支持表的关联查询、排序、分组等标准数据库操作。
  3. 事务安全:支持 ACID(原子性、一致性、隔离性、持久性)事务特性。
  4. 异步 API:全部操作基于 Promise / async-await,不阻塞 UI 线程。
  5. 锁机制:内置读写锁,支持多线程并发访问。
  6. 查询灵活:提供谓词(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  // 最高安全等级,适合敏感数据

对于用户信息管理,推荐使用 S1S2 级别。


五、数据库设计与初始化

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,保证数据的完整性。
  • agecity 设置 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');
  }
}

初始化步骤:

  1. 构建 StoreConfig:指定数据库名称和安全等级。
  2. 调用 getRdbStore():如果数据库不存在则自动创建,存在则直接打开。该方法返回 RdbStore 实例。
  3. 执行建表 SQL:使用 executeSql() 执行 CREATE TABLE IF NOT EXISTS,确保表已创建。
  4. 加载已有数据:初始化成功后立即查询所有用户并展示。
  5. 错误处理:捕获 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');
  }
}

插入操作要点:

  1. 数据验证:插入前检查 name 不能为空。
  2. 默认值处理:年龄为空时设为 0,城市为空时设为 '未知'
  3. 时间格式化:使用 padStart() 补零,生成 "2025-05-28 16:30" 格式的时间字符串。
  4. ValuesBucket 接口:键为列名,值为对应数据。注意不要包含自增主键 id
  5. 插入后刷新列表:调用 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');
  }
}

查询流程:

  1. 创建 RdbPredicates:指定要查询的表名。
  2. 设置排序orderByAsc('id') 表示按 ID 升序排列。还有 orderByDesc() 方法。
  3. 执行查询query(predicates, columns) 返回 ResultSet 结果集。
  4. 遍历结果集:使用 goToNextRow() 逐行移动游标。
  5. 读取列值:通过列索引读取,getLong(0) 读第 0 列(id),getString(1) 读第 1 列(name),以此类推。
  6. 关闭结果集必须调用 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))
}

列表设计亮点:

  1. Stack 实现圆形头像:使用 Circle + Text 组合,显示用户姓名的首字母作为头像,比图片头像更轻量。
  2. Column + Row 布局用户信息:姓名、年龄、城市在同一行,创建时间在下一行,信息层级清晰。
  3. 右侧删除按钮:使用半透明红色背景的 ✕ 按钮,鼠标悬停或触控点击即可删除,交互直观。
  4. 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();

注意事项:

  1. goToNextRow() 初始位置在第一条记录之前,第一次调用后指向第一条。
  2. 列索引从 0 开始,与 query()columns 参数的顺序一致。
  3. 每一列的类型必须与 getXxx() 方法匹配——整型用 getLong(),文本用 getString()
  4. 务必调用 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 运行步骤

  1. 使用 DevEco Studio 打开项目根目录 Demo0528
  2. 等待 Gradle 同步完成(或 Hvigor 自动配置)。
  3. 连接真机或启动模拟器(建议使用 API 12 及以上版本)。
  4. 点击运行按钮(▶️)或执行命令:
hvigorw assembleHap --no-daemon
  1. 安装 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 功能扩展

  1. 用户编辑功能:增加编辑按钮,弹出对话框修改用户信息,使用 rdbStore.update() 提交。
  2. 分页加载:使用 predicates.limit(offset, count) 实现分页查询,配合滚动加载提升大数据量性能。
  3. 数据导出:将用户列表导出为 CSV 或 Excel 文件,使用 @ohos.file.fs API 写入文件。
  4. 数据统计:使用 SQL 聚合函数(COUNT / AVG / GROUP BY)统计各城市用户数、平均年龄等。
  5. 多表关联:增加订单表、地址表,学习外键和联表查询。

13.2 UI 增强

  1. SwipeToDelete:使用 SwipeAction 组件实现左滑删除交互。
  2. 下拉刷新:使用 PullToRefresh 组件(或自定义 Scroll 刷新逻辑)。
  3. 排序切换:增加按年龄、按时间升降序切换的按钮。
  4. 空状态插图:暂无数据时显示 SVG 插画,替代简单的文字提示。
  5. 夜间模式:使用 @Styles@Extend 构建主题切换系统。

13.3 性能优化

  1. 事务批量插入:100+ 条数据时开启事务,耗时缩短 10 倍以上。
  2. 索引优化:为经常搜索的 name 列创建索引。
  3. ResultSet 关闭:使用 finally 或在 try 的结尾确保 close() 调用。
  4. 列表懒加载:使用 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:getRdbStoreexecuteSql 的区别?

A:getRdbStore 用于打开或创建数据库,返回 RdbStore 实例;executeSqlRdbStore 实例的方法,用于执行任意 SQL 语句。必须先调用 getRdbStore 获取实例,才能执行 SQL。

Q4:数据库安全等级 S1/S2/S3 有什么区别?

A:安全等级控制数据库文件的加密强度:

  • S1:最低,不加密,适合公开数据。
  • S2:中等,文件级加密,适合个人隐私数据。
  • S3:最高,全量加密,适合密钥等敏感数据。
    选择等级需要权衡性能和安全。如果存储的是公开的用户演示数据,S1 即可。

Q5:如何处理数据库版本升级?

A:getRdbStore 支持传入 StoreConfig.version 参数。当版本号增加时,系统会触发 onUpgrade 回调,开发者可以在此执行 ALTER TABLE 等迁移 SQL。但本文为了保持示例简洁,未使用版本管理。


十五、总结

本文从 HarmonyOS 的 relationalStore API 出发,通过构建一个完整的用户信息管理页面,系统地讲解了:

  1. 如何在鸿蒙应用中集成 SQLite 数据库。
  2. 如何使用 RdbPredicates 构建灵活的查询条件。
  3. 如何实现完整的 CRUD(增、删、改、查)功能。
  4. 如何使用 ArkTS 的 @State 响应式编程模型驱动 UI 刷新。
  5. 如何使用 ListTextInputButton 等基础组件构建交互界面。
  6. 如何处理异步操作中的错误和边界情况。
  7. 如何编写高效、安全的数据库访问代码。

HarmonyOS 的 relationalStore API 设计成熟而精简,既有 SQLite 的强大能力,又有现代的 async/await 异步编程模型,加上 ArkTS 的响应式 UI 框架,让本地数据存储的开发体验非常流畅。

无论是开发一个简单的通讯录、笔记应用,还是构建复杂的离线数据分析系统,本文所介绍的知识都可以直接迁移应用。希望这篇实战指南能够帮助你在鸿蒙生态的开发之路上更进一步。


十六、参考资料


本文由 AtomCode 生成,项目源代码可在 HarmonyOS 真机或模拟器上直接运行验证。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐