鸿蒙ArkData键值型数据库实战:Schema 定义与商品库存同步案例

键值型数据库(KV-Store)适合数据关系简单、又可能要跨设备同步的场景。下面用一个"商品库存管理"的完整案例,把 KVManager 创建、Schema 定义、加密、读写、订阅、关闭销毁走一遍,重点讲清楚 Schema 这个容易忽略但很关键的概念。

一、案例背景:多门店商品库存

假设一个零售应用,要管理多个门店的商品库存。数据结构很简单:商品 ID 映射到库存信息(名称、数量、是否上架)。这类数据:

  • 关系简单(就是商品 ID → 库存对象)
  • 可能要跨设备同步(手机端改了库存,平板端要看到)
  • 需要加密(库存数据敏感)

正好是 KV-Store 的主场。

二、第一步:创建 KVManager

KVManager 是管理数据库对象的入口,整个应用创建一次即可。

import { distributedKVStore } from '@kit.ArkData';
import { BusinessError } from '@kit.BasicServicesKit';

let kvManager: distributedKVStore.KVManager | undefined = undefined;
let kvStore: distributedKVStore.SingleKVStore | undefined = undefined;
const appId = 'com.example.retail';
const storeId = 'inventory_store';
const context = EntryAbility.getContext();

$0; // 占位,实际从 EntryAbility 获取

function createKvManager() {
  if (kvManager !== undefined) {
    console.info('KVManager 已创建');
    return;
  }
  const kvManagerConfig: distributedKVStore.KVManagerConfig = {
    bundleName: appId,
    context: context
  };
  try {
    kvManager = distributedKVStore.createKVManager(kvManagerConfig);
    console.info('KVManager 创建成功');
  } catch (err) {
    console.error(`创建失败: ${(err as BusinessError).message}`);
  }
}

三、第二步:定义 Schema(关键且常被忽略)

Schema 是 KV-Store 里一个容易被忽略的概念。它定义了 Value 的字段结构、类型、索引。定义 Schema 的好处:

  • 数据有约束,写入不合规数据会被拦截
  • 建了索引,查询更快
  • 分布式同步时,两端 Schema 一致才能正确同步
function getKvStore() {
  if (kvManager === undefined) {
    console.info('KVManager 未初始化');
    return;
  }
  try {
    // 定义字段节点
    let idField = new distributedKVStore.FieldNode('id');
    idField.type = distributedKVStore.ValueType.INTEGER;
    idField.nullable = false;
    idField.default = '0';

    let nameField = new distributedKVStore.FieldNode('name');
    nameField.type = distributedKVStore.ValueType.STRING;
    nameField.nullable = false;
    nameField.default = '';

    let stockField = new distributedKVStore.FieldNode('stock');
    stockField.type = distributedKVStore.ValueType.INTEGER;
    stockField.nullable = false;
    stockField.default = '0';

    // 组装 Schema
    let schema = new distributedKVStore.Schema();
    schema.root.appendChild(idField);
    schema.root.appendChild(nameField);
    schema.root.appendChild(stockField);
    schema.indexes = ['$.id', '$.name'];  // 索引路径
    schema.mode = 1;  // 1 表示 STRICT 严格模式,0 表示 COMPATIBLE
    schema.skip = 0;

    const options: distributedKVStore.Options = {
      createIfMissing: true,
      encrypt: true,           // 开启加密
      backup: false,
      autoSync: false,
      kvStoreType: distributedKVStore.KVStoreType.SINGLE_VERSION,  // 单版本数据库
      schema: schema,
      securityLevel: distributedKVStore.SecurityLevel.S3  // 安全级别 S3
    };

    kvManager.getKVStore<distributedKVStore.SingleKVStore>(
      storeId, options,
      (err, store: distributedKVStore.SingleKVStore) => {
        if (err) {
          console.error(`获取 KVStore 失败: ${err.message}`);
          return;
        }
        kvStore = store;
        console.info('KVStore 获取成功');
      }
    );
  } catch (e) {
    console.error(`异常: ${(e as BusinessError).message}`);
  }
}

几个配置项要理解清楚:

  • kvStoreTypeSINGLE_VERSION 单版本数据库;DEVICE_COLLABORATION 多设备协同数据库。不填默认多设备协同。
  • encrypt: true:开启加密,库存这种敏感数据必开
  • securityLevel: S3:安全级别,S1-S4,S3 适合一般敏感业务数据
  • schema.mode = 1:STRICT 严格模式,写入会校验字段类型;COMPATIBLE 模式更宽松

四、第三步:订阅数据变化

库存变化要实时通知 UI 刷新,用订阅:

function subscribeDataChange() {
  if (kvStore === undefined) {
    return;
  }
  try {
    kvStore.on('dataChange', distributedKVStore.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
      console.info(`数据变化: ${JSON.stringify(data)}`);
      // 这里可以通知 UI 刷新库存列表
    });
  } catch (e) {
    console.error(`订阅失败: ${(e as BusinessError).message}`);
  }
}

SUBSCRIBE_TYPE_ALL 表示订阅所有类型的变化。注意官方提醒:回调方法里不允许做阻塞操作,比如修改 UI 组件。要刷 UI 的话,通过 AppStorage 或 emitter 把数据抛出去,在 UI 线程处理。

五、第四步:写入库存数据

function putInventory() {
  if (kvStore === undefined) {
    return;
  }
  const key = 'product_1001';
  // Value 必须符合 Schema 定义的字段结构
  const value = '{"id":1001, "name":"无线耳机", "stock":128}';
  try {
    kvStore.put(key, value, (err) => {
      if (err !== undefined) {
        console.error(`写入失败: ${err.message}`);
        return;
      }
      console.info('库存写入成功');
    });
  } catch (e) {
    console.error(`异常: ${(e as BusinessError).message}`);
  }
}

关键点:当 Key 已存在时,put 会覆盖原值;不存在则新增。Value 是 JSON 字符串,字段要和 Schema 对应——STRICT 模式下,字段类型不匹配会被拒绝。

六、第五步:读取和删除

// 读取
function getInventory() {
  if (kvStore === undefined) return;
  const key = 'product_1001';
  kvStore.get(key, (err, data) => {
    if (err != undefined) {
      console.error(`读取失败: ${err.message}`);
      return;
    }
    console.info(`读取结果: ${data}`);
    // data 是 '{"id":1001, "name":"无线耳机", "stock":128}'
    let inventory = JSON.parse(data as string);
  });
}

// 删除2
function deleteInventory() {
  if (kvStore === undefined) return;
  const key = 'product_1001';
  kvStore.delete(key, (err) => {
    if (err !== undefined) {
      console.error(`删除失败: ${err.message}`);
      return;
    }
    console.info('删除成功');
  });
}

七、第六步:关闭和删除数据库

// 关闭数据库(释放资源,数据保留)
function closeKvStore() {
  if (kvManager === undefined) return;
  kvStore = undefined;
  kvManager.closeKVStore(appId, storeId, (err: BusinessError) => {
    if (err) {
      console.error(`关闭失败: ${err.message}`);
      return;
    }
    console.info('关闭成功');
  });
}

// 删除数据库(数据也删除)
function deleteKvStore() {
  if (kvManager === undefined) return;
  kvStore = undefined;
  kvManager.deleteKVStore(appId, storeId, (err: BusinessError) => {
    if (err) {
      console.error(`删除失败: ${err.message}`);
      return;
    }
    console.info('删除成功');
  });
}

八、约束限制速查

约束 说明
Key 长度 单版本 ≤ 1KB;设备协同 ≤ 896 Byte
Value 长度 < 4MB
同时打开数据库数 每应用最多 16 个
回调里阻塞操作 不允许(如改 UI)

九、单版本 vs 多设备协同:怎么选

KV-Store 有两种数据库类型,选哪个看同步需求:

  • SINGLE_VERSION(单版本):本地数据库,不自动跨设备同步。适合纯本地存储,或手动控制同步时机。
  • DEVICE_COLLABORATION(多设备协同):支持跨设备自动同步。适合需要多端实时一致的场景。

本案例如果只是单门店本地管理,用 SINGLE_VERSION 就够。如果要多门店跨设备同步库存,用 DEVICE_COLLAB)LABORATION,并配合分布式同步接口(下一篇关系型会详细讲,KV 的同步思路类似)。

十、实战封装:InventoryRepository

把散装 API 封装成仓储类,业务侧更干净:

export class InventoryRepository {
  private store: distributedKVStore.SingleKVStore | undefined;

  constructor(store: distributedKVStore.SingleKVStore) {
    this.store = store;
  }

  async save(productId: number, name: string, stock: number): Promise<void> {
    return new Promise((resolve, reject) => {
      if (!this.store) { reject(new Error('store 未初始化')); return; }
      const key = `product_${productId}`;
      const value = JSON.stringify({ id: productId, name, stock });
      this.store.put(key, value, (err) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }

  async get(productId: number): Promise<{id: number, name: string, stock: number} | null> {
    return new Promise((resolve, reject) => {
      if (!this.store) { reject(new Error('store 未初始化')); return; }
     $      this.store.get(`product_${productId}`, (err, data) => {
        if (err) reject(err);
        else if (data) resolve(JSON.parse(data as string));
        else resolve(null);
      });
    });
  }
}

十一、几条经验哦

  1. Schema 不是可选项:定义字段、类型、索引,既能约束数据,又能加速查询和同步
  2. 敏感数据开 encrypt:KV-Store 原生支持加密,不用自己加解密
  3. 回调里别做阻塞操作:要刷 UI 用 AppStorage/emitter 抛出去
  4. 选对数据库类型:本地用 SINGLE_VERSION,跨设备用 DEVICE_COLLABORATION
  5. Value 是 JSON 字符串:字段要和 Schema 对应,STRICT 模式会校验

下一篇进入关系型数据库,处理更复杂的数据关系和 SQL 场景。

Logo

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

更多推荐