鸿蒙学习实战之路-数据持久化关系型数据库 RelationalStore 全攻略

最近好多朋友问我:“西兰花啊,我在鸿蒙里存复杂关系数据总出错,用 SQL 又怕麻烦,咋办?” 害,这问题可问对人了!

今天这篇,我就手把手带你搞定鸿蒙里的关系型数据库(RelationalStore),从基础概念到实战代码,全程用大白话讲明白~

一、什么是应用数据持久化?

应用数据持久化是指应用将内存中的数据通过文件或数据库的形式保存到设备上。内存中的数据形态通常是任意的数据结构或数据对象,存储介质上的数据形态可能是文本、数据库、二进制文件等。

HarmonyOS 标准系统支持典型的存储数据形态,包括:

  • 用户首选项(Preferences):用于保存应用的配置信息。数据以文本形式保存在设备中,应用使用时会将文本中的数据全量加载到内存中,访问速度快、效率高,但不适合需要存储大量数据的场景。

  • 键值型数据库(KV-Store):一种非关系型数据库,其数据以"键值"对的形式进行组织、索引和存储,其中"键"作为唯一标识符。适合数据关系和业务关系较少的业务数据存储,同时因其在分布式场景中降低了解决数据库版本兼容问题的复杂度和数据同步过程中冲突解决的复杂度而被广泛使用。相比于关系型数据库,更容易做到跨设备跨版本兼容。

  • 关系型数据库(RelationalStore):一种关系型数据库,以行和列的形式存储数据,广泛用于关系型数据处理,支持增、删、改、查等接口,开发者也可以运行自定义 SQL 语句满足复杂业务场景。此外,提供了向量数据库能力,支持向量数据间的相似度计算,适用于推荐场景、相似图像检索以及自然语言处理等。

今天咱们重点聊聊关系型数据库(RelationalStore),这玩意儿就像咱们家里的收纳架,每个格子都有明确的分类和位置,特别适合存放有复杂关系的数据!

二、什么场景用 RelationalStore?

关系型数据库基于 SQLite 组件,就像厨房的收纳架,特别适合存放有复杂关系的数据,比如:

  • 班级学生信息(姓名、学号、各科成绩之间的关系)
  • 公司雇员信息(姓名、工号、职位的层级关系)
  • 电商订单数据(用户、商品、订单之间的关联)

🥦 西兰花警告
单次查询数据量别超过 5000 条啊!超过了建议用 TaskPool 异步执行,不然可能卡顿~

二、RelationalStore 运作机制

关系型数据库对应用提供通用操作接口,底层使用 SQLite 作为持久化存储引擎,支持事务、索引、视图、触发器、外键等 SQLite 特性。就像你用收纳盒分类放食材,RelationalStore 帮你把数据按表、行、列整理得整整齐齐~

图 1 关系型数据库运作机制
关系型数据库运作机制

三、约束限制要记牢

限制项 具体说明
日志与落盘 默认 WAL(Write Ahead Log)模式,就像你做饭前先写菜谱,安全又可靠
连接管理 常驻 4 个读连接和 1 个写连接,读连接不够了还能动态扩充
写操作限制 同一时间仅支持一个写操作,就像厨房只能一个人炒菜,不然要撞锅铲~
数据类型 支持 number、string、二进制类型、boolean,基本够日常用了
数据大小 单条数据建议不超过 2M,超出可能导致读取失败!
应用卸载 相关数据库文件及临时文件会自动清除,不用担心占空间

🥦 西兰花警告
单条数据真的别超 2MB 啊!我有个朋友存了大图片进去,结果读取时直接崩溃,debug 了一下午才找到原因~

四、核心接口速览

关系型数据库的核心接口支持 callback 和 Promise 两种形式,就像你可以打电话或者发微信联系我一样方便~

接口名称 描述
getRdbStore 获取 RdbStore 实例,数据库操作的入口
createTransaction 创建事务对象,保证操作的原子性
execute 执行 SQL 语句,想咋操作咋操作
querySql 执行查询 SQL 并返回结果集
insert 插入一行数据
update 更新符合条件的数据
delete 删除符合条件的数据
query 查询数据,支持谓词条件筛选
deleteRdbStore 删除数据库,谨慎使用!

更多接口详见关系型数据库官方文档

五、实战操作:从 0 到 1 使用 RelationalStore

1. 获取 RdbStore 实例

Stage 模型

import { relationalStore } from "@kit.ArkData";
import { UIAbility } from "@kit.AbilityKit";
import { BusinessError } from "@kit.BasicServicesKit";

export default class EntryAbility extends UIAbility {
  onWindowStageCreate(windowStage) {
    // 检查分词器支持性,就像先看看厨房有没有需要的调料
    let tokenType = relationalStore.Tokenizer.ICU_TOKENIZER;
    let tokenTypeSupported = relationalStore.isTokenizerSupported(tokenType);
    if (!tokenTypeSupported) {
      console.error(`ICU_TOKENIZER在当前平台不支持。`);
    }

    // 配置数据库信息
    const STORE_CONFIG: relationalStore.StoreConfig = {
      name: "RdbTest.db", // 数据库文件名
      securityLevel: relationalStore.SecurityLevel.S3, // 安全级别
      encrypt: false, // 是否加密
      customDir: "customDir/subCustomDir", // 自定义路径
      isReadOnly: false, // 是否只读
      tokenizer: tokenType, // 分词器类型
    };

    // 创建表SQL语句
    const SQL_CREATE_TABLE =
      "CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB, IDENTITY UNLIMITED INT)";

    // 获取数据库实例
    relationalStore.getRdbStore(
      this.context,
      STORE_CONFIG,
      async (err, store) => {
        if (err) {
          console.error(
            `获取RdbStore失败。错误码:${err.code}, 错误信息:${err.message}`
          );
          return;
        }

        let storeVersion = store.version;
        // 数据库版本管理
        if (storeVersion === 0) {
          try {
            // 创建表
            await store.execute(SQL_CREATE_TABLE);
            storeVersion = 3; // 设置初始版本
          } catch (e) {
            const err = e as BusinessError;
            console.error(
              `执行SQL失败。错误码:${err.code}, 错误信息:${err.message}`
            );
          }
        }

        // 版本升级示例
        if (storeVersion === 1) {
          await store.execute("ALTER TABLE EMPLOYEE ADD COLUMN AGE INTEGER");
          storeVersion = 2;
        }
        if (storeVersion === 2) {
          await store.execute("ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS");
          storeVersion = 3;
        }

        // 更新数据库版本
        store.version = storeVersion;
      }
    );
  }
}

FA 模型

import { relationalStore } from "@kit.ArkData";
import { featureAbility } from "@kit.AbilityKit";

// 获取上下文
let context = featureAbility.getContext();
// 配置数据库信息
const STORE_CONFIG: relationalStore.StoreConfig = {
  name: "RdbTest.db",
  securityLevel: relationalStore.SecurityLevel.S3,
};

// 创建表SQL语句
const SQL_CREATE_TABLE =
  "CREATE TABLE IF NOT EXISTS EMPLOYEE (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT NOT NULL, AGE INTEGER, SALARY REAL, CODES BLOB, IDENTITY UNLIMITED INT)";

// 获取数据库实例
relationalStore.getRdbStore(context, STORE_CONFIG, async (err, store) => {
  if (err) {
    console.error(
      `获取RdbStore失败。错误码:${err.code}, 错误信息:${err.message}`
    );
    return;
  }

  // 版本管理逻辑与Stage模型类似
});

🥦 西兰花小贴士
建议在 UIAbility 的 onWindowStageCreate 方法中初始化数据库,这样能确保上下文可用,避免出现奇怪的错误~

2. 插入数据

// 准备要插入的数据
let employeeName = "Lisa";
let employeeAge = 18;
let employeeSalary = 100.5;
let employeeCodes = new Uint8Array([1, 2, 3, 4, 5]);
let employeeIdentity = BigInt("15822401018187971961171");

// 组织数据
const employeeData: relationalStore.ValuesBucket = {
  NAME: employeeName,
  AGE: employeeAge,
  SALARY: employeeSalary,
  CODES: employeeCodes,
  IDENTITY: employeeIdentity,
};

// 插入数据
if (store !== undefined) {
  try {
    const rowId = await store.insert("EMPLOYEE", employeeData);
    console.info(`插入数据成功。行ID:${rowId}`);
  } catch (error) {
    const err = error as BusinessError;
    console.error(`插入数据失败。错误码:${err.code}, 错误信息:${err.message}`);
  }
}

3. 更新数据

// 准备更新的数据
const updateData: relationalStore.ValuesBucket = {
  NAME: "Rose",
  AGE: 22,
  SALARY: 200.5,
  CODES: new Uint8Array([1, 2, 3, 4, 5]),
  IDENTITY: BigInt("15822401018187971967863"),
};

// 创建谓词条件(相当于WHERE子句)
let updateCondition = new relationalStore.RdbPredicates("EMPLOYEE");
updateCondition.equalTo("NAME", "Lisa");

// 执行更新
if (store !== undefined) {
  (store as relationalStore.RdbStore).update(
    updateData,
    updateCondition,
    (err, rows) => {
      if (err) {
        console.error(
          `更新数据失败。错误码:${err.code}, 错误信息:${err.message}`
        );
        return;
      }
      console.info(`更新数据成功。更新行数: ${rows}`);
    }
  );
}

4. 删除数据

// 创建谓词条件
let deleteCondition = new relationalStore.RdbPredicates("EMPLOYEE");
deleteCondition.equalTo("NAME", "Lisa");

// 执行删除
if (store !== undefined) {
  (store as relationalStore.RdbStore).delete(deleteCondition, (err, rows) => {
    if (err) {
      console.error(
        `删除数据失败。错误码:${err.code}, 错误信息:${err.message}`
      );
      return;
    }
    console.info(`删除成功。删除行数: ${rows}`);
  });
}

5. 查询数据

// 创建谓词条件
let queryCondition = new relationalStore.RdbPredicates("EMPLOYEE");
queryCondition.equalTo("NAME", "Rose");

// 执行查询
if (store !== undefined) {
  (store as relationalStore.RdbStore).query(
    queryCondition,
    ["ID", "NAME", "AGE", "SALARY", "IDENTITY"],
    (err, resultSet) => {
      if (err) {
        console.error(
          `查询数据失败。错误码:${err.code}, 错误信息:${err.message}`
        );
        return;
      }

      console.info(
        `结果集列名: ${resultSet.columnNames}, 列数: ${resultSet.columnCount}`
      );

      // 遍历结果集
      while (resultSet.goToNextRow()) {
        const id = resultSet.getLong(resultSet.getColumnIndex("ID"));
        const name = resultSet.getString(resultSet.getColumnIndex("NAME"));
        const age = resultSet.getLong(resultSet.getColumnIndex("AGE"));
        const salary = resultSet.getDouble(resultSet.getColumnIndex("SALARY"));
        const identity = resultSet.getValue(
          resultSet.getColumnIndex("IDENTITY")
        );
        console.info(
          `员工信息: id=${id}, 姓名=${name}, 年龄=${age}, 薪资=${salary}, 身份标识=${identity}`
        );
      }

      // 释放资源,就像用完厨房要收拾干净一样
      resultSet.close();
    }
  );
}

🥦 西兰花小贴士
查询完一定要记得调用 resultSet.close()释放资源啊!不然会内存泄漏,影响应用性能~

6. 事务操作

事务就像你做饭,要么全做完,要么全不做,不会出现做了一半的情况~

if (store !== undefined) {
  try {
    // 创建事务
    const transaction = await store.createTransaction();

    try {
      // 插入数据
      const rowId = await transaction.insert(
        "EMPLOYEE",
        {
          NAME: "Lisa",
          AGE: 18,
          SALARY: 100.5,
          CODES: new Uint8Array([1, 2, 3, 4, 5]),
        },
        relationalStore.ConflictResolution.ON_CONFLICT_REPLACE
      );
      console.info(`插入成功,行ID = ${rowId}`);

      // 更新数据
      const predicates = new relationalStore.RdbPredicates("EMPLOYEE");
      predicates.equalTo("NAME", "Lisa");
      const rows = await transaction.update(
        { NAME: "Rose", AGE: 22 },
        predicates,
        relationalStore.ConflictResolution.ON_CONFLICT_REPLACE
      );
      console.info(`更新行数: ${rows}`);

      // 提交事务
      await transaction.commit();
      console.info("事务提交成功。");
    } catch (error) {
      // 回滚事务,就像做饭失败了重新来
      await transaction.rollback();
      const err = error as BusinessError;
      console.error(`事务失败。错误码:${err.code}, 错误信息:${err.message}`);
    }
  } catch (error) {
    const err = error as BusinessError;
    console.error(`创建事务失败。错误码:${err.code}, 错误信息:${err.message}`);
  }
}

7. 数据库备份与恢复

备份数据库

if (store !== undefined) {
  (store as relationalStore.RdbStore).backup("Backup.db", (err) => {
    if (err) {
      console.error(
        `备份数据库失败。错误码:${err.code}, 错误信息:${err.message}`
      );
      return;
    }
    console.info(`备份数据库成功。`);
  });
}

恢复数据库

if (store !== undefined) {
  (store as relationalStore.RdbStore).restore("Backup.db", (err) => {
    if (err) {
      console.error(
        `恢复数据库失败。错误码:${err.code}, 错误信息:${err.message}`
      );
      return;
    }
    console.info(`恢复数据库成功。`);
  });
}

🥦 西兰花小贴士
重要数据一定要定期备份啊!就像你会定期备份手机照片一样,以防万一~

8. 删除数据库

// Stage模型
relationalStore.deleteRdbStore(this.context, "RdbTest.db", (err) => {
  if (err) {
    console.error(
      `删除数据库失败。错误码:${err.code}, 错误信息:${err.message}`
    );
    return;
  }
  console.info("删除数据库成功。");
});

// FA模型
relationalStore.deleteRdbStore(context, "RdbTest.db", (err) => {
  // 处理结果
});

🥦 西兰花警告
删除数据库是不可逆的操作!一定要谨慎再谨慎,最好先备份再删除~

六、完整示例代码

如果你想直接运行看看效果,可以参考官方示例:

七、总结

关系型数据库 RelationalStore 就像鸿蒙里的"厨房收纳架",特别适合存放有复杂关系的数据。它基于 SQLite,支持事务、SQL 查询等强大功能,能满足大部分复杂业务场景的需求。

📚 推荐资料:

我是盐焗西兰花,
不教理论,只给你能跑的代码和避坑指南。
下期见!🥦

Logo

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

更多推荐