鸿蒙ArkData关系型数据库 CRUD 实战:员工管理系统的建库到查询

关系型数据库(RelationalStore)是 ArkData 里能力最全的存储,基于 SQLite,支持事务、索引、谓词查询、自定义 SQL。下面用一个"员工管理系统"的完整案例,把建库建表、版本升级、增删改查、谓词查询、结果集遍历走一遍,重点讲清楚版本升级用事务保证原子性这个最佳实践。

一、案例背景:员工信息管理

一个公司员工管理系统,员工信息包括:姓名、年龄、薪资、编码(二进制)、地址。数据有明显的关系结构,适合关系型数据库。

表结构设计:

CREATE TABLE IF NOT EXISTS EMPLOYEE (
  ID INTEGER PRIMARY KEY AUTOINCREMENT,
  NAME TEXT NOT NULL,
  AGE INTEGER,
  SALARY REAL,
  CODES BLOB,
  ADDRESS TEXT
)

二、第一步:配置 StoreConfig

import { relationalStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { common } from '@kit.AbilityKit';

const DOMAIN = 0x0000;
let store: relationalStore.RdbStore | undefined = undefined;

const STORE_CONFIG: relationalStore.StoreConfig = {
  name: 'RdbTest.db',                    // 数据库文件名
  securityLevel: relationalStore.SecurityLevel.S3,  // 安全级别
  encrypt: false,                        // 是否加密
  customDir: 'customDir/subCustomDir',   // 自定义路径(可选)
  isReadOnly: false,                     // 是否只读
};

几个配置要点:

  • name:数据库文件名,不同 context 下同名会产生不同数据库
  • securityLevel:S1-S4,按数据敏感度选
  • encrypt:敏感数据建议开
  • isReadOnly: true 时只允许读,写会返回错误码 801

三、第二步:建库建表 + 版本升级(事务保证原子性)

这是官方示例里最值得学习的部分——用事务保证数据库升级流程的原子性。升级过程中任何一步失败,整个升级回滚,不会留下半升级的烂摊子。

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, ADDRESS TEXT)';

async function initDatabase(context: common.UIAbilityContext) {
  if (store === undefined) {
    try {
      store = await relationalStore.getRdbStore(context, STORE_CONFIG);
    } catch (e) {
      const err = e as BusinessError;
      hilog.error(DOMAIN, 'rdb', `获取 RdbStore 失败: ${err.message}`);
      return;
    }
  }

  if (store !== undefined) {
    // 创建事务对象
    let transaction = await store.createTransaction({});
    // 读取当前数据库版本
    let storeVersion = await transaction.execute('PRAGMA user_version');
    
    // 版本 0 → 1:初次建表
    if (storeVersion === 0) {
      try {
        await transaction.execute(SQL_CREATE_TABLE);
        storeVersion = 1;
        hilog.info(DOMAIN, 'rdb', '升级 0 → 1 成功');
      } catch (e) {
        await transaction.rollback();  // 失败回滚
        hilog.error(DOMAIN, 'rdb', `建表失败: ${(e as BusinessError).message}`);
        return;
      }
    }
    
    // 版本 1 → 2:新增 IDENTITY 列
    if (storeVersion === 1) {
      try {
        await transaction.execute('ALTER TABLE EMPLOYEE ADD COLUMN IDENTITY UNLIMITED INT');
        storeVersion = 2;
        hilog.info(DOMAIN, 'rdb', '升级 1 → 2 成功');
      } catch (e) {
        await transaction.rollback();
        hilog.error(DOMAIN, 'rdb', `升级失败: ${(e as BusinessError).message}`);
        return;
      }
    }
    
    // 版本 2 → 3:删除 ADDRESS 列
    if (storeVersion === 2) {
      try {
        await transaction.execute('ALTER TABLE EMPLOYEE DROP COLUMN ADDRESS');
        storeVersion = 3;
        await transaction.execute('PRAGMA user_version = 3');
        hilog.info(DOMAIN, 'rdb', '升级 2 → 3 成功');
      } catch (e) {
        await transaction.rollback();
        hilog.error(DOMAIN, 'rdb', `升级失败: ${(e as BusinessError).message}`);
        return;
      }
    }
    
    await transaction.commit();  // 全部成功,提交事务
  }
}

为什么用事务:升级逻辑较多时,如果不用事务,中途失败会导致数据库处于中间版本,后续启动无法正确判断状态。事务保证"要么全升完,要么完全不升",状态始终一致。

官方也提醒:升级逻辑多时建议拆成多个独立事务串行执行,本例因为流程短用了单个事务。

四、第三步:插入数据

async function insertEmployee() {
  const valueBucket: relationalStore.ValuesBucket = {
    NAME: 'Lisa',
    AGE: 18,
    SALARY: 100.5,
    CODES: new Uint8Array([1, 2, 3, 4, 5]),  // BLOB 类型用 Uint8Array
    IDENTITY: BigInt('15822401018187971961171'),  // bigint 用 UNLIMITED INT
  };
  if (store !== undefined) {
    try {
      const rowId = await store.insert('EMPLOYEE', valueBucket);
      hilog.info(DOMAIN, 'rdb', `插入成功,rowId: ${rowId}`);
    } catch (error) {
      hilog.error(DOMAIN, 'rdb', `插入失败: ${(error as BusinessError).message}`);
    }
  }
}

要点:

  • 关系型数据库没有显式 flush:插入即持久化,不需要像 Preferences 那样调 flush
  • BLOB 用 Uint8Array:二进制数据(图片缩略图、加密密文等)用 Uint8Array
  • bigint 用 UNLIMITED INT:超大整数在 SQL 里声明为 UNLIMITED INT
  • 单条数据建议不超过 2MB:超过能插入但读取会失败

五、第四步:更新和删除(谓词驱动)

关系型数据库的更新和删除用**谓词(RdbPredicates)**指定条件,比拼 SQL 字符串更安全(防注入)。

// 更新:把 NAME 为 'Lisa' 的员工信息改掉
async function updateEmployee() {
  const valueBucket: relationalStore.ValuesBucket = {
    NAME: 'Rose',
    AGE: 22,
    SALARY: 200.5,
    CODES: new Uint8Array([1, 2, 3, 4, 5]),
    IDENTITY: BigInt('15822401018187971967863'),
  };

  let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
  predicates.equalTo('NAME', 'Lisa');  // 条件:NAME = 'Lisa'

  if (store !== undefined) {
    const rows = await store.update(valueBucket, predicates);
    hilog.info(DOMAIN, 'rdb', `更新行数: ${rows}`);
  }
}

// 删除:删掉 NAME 为 'Lisa' 的员工
async function deleteEmployee() {
  let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
  predicates.equalTo('NAME', 'Lisa');

  if (store !== undefined) {
    const rows = await store.delete(predicates);
    hilog.info(DOMAIN, 'rdb', `删除行数: ${rows}`);
  }
}

谓词支持链式调用,能拼出复杂条件:

let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
predicates.greaterThan('AGE', 20)
          .and()
          .lessThan('SALARY', 200)
          .orderByAsc('AGE');

六、第五步:查询与结果集遍历

async function queryEmployee() {
  let predicates = new relationalStore.RdbPredicates('EMPLOYEE');
  predicates.equalTo('NAME', 'Rose');

  if (store !== undefined) {
    const resultSet = await store.query(predicates, ['ID', 'NAME', 'AGE', 'SALARY', 'CODES', 'IDENTITY']);
    hilog.info(DOMAIN, 'rdb', `列名: ${resultSet.columnNames}, 列数: ${resultSet.columnCount}`);
    
    // resultSet 是游标,默认指向第 -1 条,有效数据从 0 开始
    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'));
      hilog.info(DOMAIN, 'rdb', `id=${id}, name=${name}, age=${age}, salary=${salary}`);
    }
    
    // 用完必须关闭,释放内存
    resultSet.close();
  }
}

结果集使用三步曲

  1. goToNextRow() 移动游标,返回 false 表示遍历完
  2. getColumnIndex('字段名') 拿列索引,再 getLong/getString/getDouble 取值
  3. close() 释放内存——这步最容易被忘,长期不 close 会内存泄漏

七、第六步:自定义 SQL 查询

复杂查询用谓词写不出来时,直接跑 SQL:

async function customQuery() {
  if (store !== undefined) {
    // querySql 用于查询,返回结果集
    const resultSet = await store.querySql(
      'SELECT NAME, AVG(SALARY) as avg_salary FROM EMPLOYEE GROUP BY NAME HAVING AVG(SALARY) > ?',
      [100]
    );
    while (resultSet.goToNextRow()) {
      const name = resultSet.getString(resultSet.getColumnIndex('NAME'));
      const avgSalary = resultSet.getDouble(resultSet.getColumnIndex('avg_salary'));
      hilog.info(DOMAIN, 'rdb', `${name} 平均薪资: ${avgSalary}`);
    }
    resultSet.close();
  }
}

querySql 第二个参数是参数化绑定(? 占位),能防 SQL 注入。永远不要用字符串拼接构造 SQL,那是安全漏洞的温床。

八、第七步:删除数据库

async function deleteDatabase(context: common.UIAbilityContext) {
  relationalStore.deleteRdbStore(context, 'RdbTest.db', (err: BusinessError) => {
    if (err) {
      hilog.error(DOMAIN, 'rdb', `删除失败: ${err.message}`);
      return;
    }
    hilog.info(DOMAIN, 'rdb', '数据库删除成功');
  });
}

九、约束限制速查

约束 说明
默认日志模式 WAL(Write Ahead Log)
默认落盘方式 FULL 模式
连接数 常驻 4 读 + 1 写,读可动态扩充,写不可
并发写 同一时间只支持一个写操作
单条数据大小 建议 ≤ 2MB,超过能插但读失败
卸载 数据库文件及临时文件自动清除
支持类型 number、string、二进制、boolean

十、几个容易出问题的地方

10.1 -wal 和 -shm 临时文件

使用数据库时,同目录下会产生 -wal-shm 结尾的临时文件。如果要移动数据库文件到别处查看,必须连同这两个临时文件一起移动,否则数据可能不完整。

10.2 大数据量查询卡顿

官方明确建议:

  • 单次查询数据量不超过 5000 条
  • 在 TaskPool 中查询(避免阻塞主线程)
  • SQL 拼接尽量简洁
  • 合理分批次查询

10.3 数据库异常重建

操作过程中可能抛出错误码 14800011,这是非预期的数据库异常。此时需要重建数据库并恢复数据,具体见下一篇的备份恢复实战。

10.4 context 要对

getRdbStore 和 context 强相关。即使用同名数据库,不同 context(比如不同 UIAbility)会产生不同数据库。多 UIAbility 共享数据时要注意 context 的选择。

十一、总结一下下

  1. 版本升级用事务:保证原子性,失败回滚,状态一致
  2. 插入即持久化:不需要 flush
  3. 更新删除用谓词:防注入,链式条件更清晰
  4. 结果集必须 close:否则内存泄漏
  5. SQL 用参数化绑定:永远不要字符串拼接
  6. 大数据量分批查:单次 ≤5000 条,放 TaskPool 里跑
Logo

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

更多推荐