在 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_timeDate.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. 主键自增传值错误

  • 错误示例:新增时id0undefined
  • 解决方案:新增时id必须传null,让数据库自动生成自增 ID。

3. 数据类型不匹配

  • 错误示例:用getString()获取create_time(整数型);
  • 解决方案:严格匹配字段类型:
    • INTEGERgetLong()
    • TEXTgetString()
    • REALgetDouble()

4. 异步操作未等待

  • 错误示例:createStore未执行完就调用insert,导致storeundefined
  • 解决方案:数据库初始化放在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实现了完整的关系型数据库增删改查示例,核心要点可总结为:

  1. 初始化流程:先通过getRdbStore创建数据库连接,再执行CREATE TABLE初始化表结构;
  2. 查询核心ResultSet需手动遍历和关闭,字段类型需与getLong/getString匹配;
  3. 条件操作RdbPredicates替代原生 SQL 条件,简化查询 / 修改 / 删除的条件构造;
  4. 资源管理:ResultSet 必须关闭,数据库操作完成后按需释放RdbStore实例;
  5. 最佳实践:封装工具类、使用事务、分页查询、添加索引,提升代码可维护性和性能。

掌握RelationalStore的核心用法后,可轻松应对鸿蒙应用中结构化数据的存储需求,无论是文章管理、用户中心还是订单系统,都能基于这套逻辑快速实现。

Logo

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

更多推荐