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

一、引言

在移动应用开发中,本地数据持久化是一个永恒的核心需求。无论是用户信息缓存、离线数据浏览,还是复杂查询分析,本地数据库都扮演着不可或缺的角色。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 Studio5.0+
HarmonyOS SDKAPI 12+
ArkTS 版本5.0+
Node.js18+(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');
  }
}

初始化步骤:

  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: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 出发,通过构建一个完整的用户信息管理页面,系统地讲解了:

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

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

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


十六、参考资料


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

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐