28 ValuesBucket 数据插入与实体映射

前言

在这里插入图片描述

图:28 ValuesBucket 数据插入与实体映射 运行效果截图(HarmonyOS NEXT)

在鸿蒙 RDB 中,数据的插入通过 store.insert(table, values) 方法完成,其中 values 的类型是 ValuesBucket——本质上是一个键值对映射,键是列名,值是要插入的数据。

本文将深入解析 ValuesBucket 的构建方法、实体→ValuesBucket 的映射模式,以及项目 5 个 DAO 中 insert 操作的最佳实践。

鸿蒙官方·ValuesBucket 文档:developer.huawei.com
项目源码仓库:harmony-app GitHub

ValuesBucket 实体映射流程

图:实体对象 → ValuesBucket → RDB Insert 完整流程

ArkTS 实体对象
Handwriting / User

toValuesBucket 方法

ValuesBucket
键值对映射

store.insert
tableName, values

返回 rowId
新插入记录的 ID

抛出异常
插入失败

一、ValuesBucket 基础

1.1 定义

ValuesBucket 是一个类型别名,本质上是 Record<string, number | string | boolean | Uint8Array | null>

// 类型定义
type ValuesBucket = Record<string, number | string | boolean | Uint8Array | null>

// 最简单的使用
const values: relationalStore.ValuesBucket = {
  open_id: 'local',
  name: '鹿鹿',
  created_at: Date.now()
}

1.2 值类型对照表

ArkTS 类型 ValuesBucket 类型 插入到 SQL 中的类型 Getter
number number INTEGER / REAL getLong() / getDouble()
string string TEXT getString()
boolean boolean INTEGER(0/1) getLong()
Uint8Array Uint8Array BLOB getBlob()
null/undefined 不传或 null NULL 判断后再读取

二、各 DAO 的 ValuesBucket 构建

2.1 UserDao:最简单的插入

// UserDao.ensureLocalUser
const values: relationalStore.ValuesBucket = {
  open_id: 'local',
  name: name,           // 方法参数传入
  created_at: Date.now()
}
const id = await store.insert(TABLE, values)

特点:只插入 3 个字段,其余字段(avatar)有 DEFAULT 值或不填。

2.2 HandwritingDao:完整的字段映射

static async create(item: HandwritingEntity): Promise<number> {
  const store = HandwritingDao.getStore()
  const values: relationalStore.ValuesBucket = {
    archive_id: item.archive_id ?? 0,         // ?? 处理可选字段
    source: item.source,                       // NOT NULL 字段
    image_path: item.image_path ?? '',
    ocr_text: item.ocr_text ?? '',
    feature_json: item.feature_json ?? '',
    energy_score: item.energy_score ?? 0,
    device_type: item.device_type ?? '',
    word_count: item.word_count ?? 0,
    write_duration: item.write_duration ?? 0,
    created_at: item.created_at
  }
  return await store.insert(TABLE, values)
}

2.3 各 DAO 的 insert 映射对比

DAO 插入字段数 必传字段 可选字段处理
UserDao 3 open_id, name, created_at 不传(SQL DEFAULT)
HandwritingDao 10 source, created_at ?? 提供默认值
ArchiveDao 8 user_id, name, created_at ?? ''?? 0
ReportDao 7 handwriting_id, created_at ?? ''?? 0
RelationDao 5 user_id, partner_archive_id, created_at ?? ''?? 0

三、字段默认值策略

3.1 ?? 操作符的应用

项目中大量使用 ?? 空值合并操作符来处理可选字段:

const values: relationalStore.ValuesBucket = {
  archive_id: item.archive_id ?? 0,       // undefined → 0(未归类)
  image_path: item.image_path ?? '',       // undefined → 空字符串
  ocr_text: item.ocr_text ?? '',
  feature_json: item.feature_json ?? '',
  energy_score: item.energy_score ?? 0,
  device_type: item.device_type ?? '',     // v2 新增字段
  word_count: item.word_count ?? 0,
  write_duration: item.write_duration ?? 0,
}

?? 与 || 的区别:

操作符 false 0 '' undefined null
a ?? 'default' false 0 '' 'default' 'default'
a || 'default' 'default' 'default' 'default' 'default' 'default'

提示:?? 只会在 undefinednull 时使用默认值。这对于数值字段(如 energy_score: 0)非常重要——|| 会把 0 当作 falsy 值替换成默认值,而 ?? 正确保留 0

3.2 各字段类型的默认值

SQL 类型 ArkTS 类型 ValuesBucket 默认值 含义
INTEGER (外键) number | undefined 0 0 表示"未关联"
INTEGER (数值) number | undefined 0 无数据
TEXT (内容) string | undefined '' 空字符串
TEXT (路径) string | undefined '' 无路径
INTEGER (NOT NULL) number(必传) 无默认值 必须传入有效值

四、insert 的返回值

4.1 返回值说明

const id = await store.insert(TABLE, values)
// 返回值类型:number
// 成功时:新插入行的 row ID(自增主键值)
// 失败时:抛出异常

4.2 典型用法

// HandwritingDao.create — 返回 ID,供后续创建报告使用
const handwritingId = await HandwritingDao.create({
  archive_id: archiveId,
  source: 'camera',
  ocr_text: ocrText,
  feature_json: JSON.stringify(features),
  created_at: Date.now()
})

// 使用返回的 ID 创建关联报告
await ReportDao.create({
  handwriting_id: handwritingId,
  archive_id: archiveId,
  personality_type: result.type,
  radar_json: JSON.stringify(result.scores),
  created_at: Date.now()
})

五、预置的 ValuesBucket 模式

5.1 直接字面量

// UserDao — 简单,字段少
const id = await store.insert(TABLE, {
  open_id: 'local',
  name,
  created_at: Date.now()
})

5.2 从 Entity 映射

// HandwritingDao — 从 HandwritingEntity 对象映射
const values: relationalStore.ValuesBucket = {
  archive_id: item.archive_id ?? 0,
  source: item.source,
  image_path: item.image_path ?? '',
  // ...
}

5.3 手动构造

// DemoSeeder — 在循环中构造
const handwritingId = await HandwritingDao.create({
  archive_id: archiveId,
  source: 'demo',
  image_path: '',
  ocr_text: p.text,
  feature_json: JSON.stringify(p.scores),
  energy_score: p.scores.energy,
  created_at: timestamp
})

六、写入性能优化建议

6.1 批量插入

当需要插入多条记录时,逐条 insert 效率较低:

// ❌ 逐条插入(DemoSeeder 中目前的方式)
for (const item of items) {
  await HandwritingDao.create(item)
}

// ✅ 批量插入优化(使用事务)
static async batchCreate(items: HandwritingEntity[]): Promise<void> {
  const store = HandwritingDao.getStore()
  await store.executeSql('BEGIN TRANSACTION')
  try {
    for (const item of items) {
      const values: relationalStore.ValuesBucket = {
        /* ... */
      }
      await store.insert(TABLE, values)
    }
    await store.executeSql('COMMIT')
  } catch (e) {
    await store.executeSql('ROLLBACK')
    throw e
  }
}

6.2 事务性能对比

方式 插入 10 条 插入 100 条 说明
逐条插入(无事务) ~50ms ~500ms 每条 commit 一次
显式事务 ~5ms ~30ms 一次 commit
批量 prepare(SQLite) ~3ms ~20ms 最优化

七、常见错误排查

7.1 字段名不匹配

// ❌ 错误:字段名拼写错误
const values: relationalStore.ValuesBucket = {
  create_at: Date.now()  // 拼写错误:应该是 created_at
}
// SQLite 会静默忽略不存在的列名

// ✅ 正确:与 CREATE TABLE 中的列名完全一致
const values: relationalStore.ValuesBucket = {
  created_at: Date.now()
}

7.2 NOT NULL 约束冲突

// ❌ 错误:NOT NULL 字段没有传入
const values: relationalStore.ValuesBucket = {}
await store.insert(TABLE, values)
// → 抛出异常:"NOT NULL constraint failed: user.name"

// ✅ 正确:所有 NOT NULL 字段都有值
const values: relationalStore.ValuesBucket = {
  source: 'camera',
  created_at: Date.now()
}

7.3 类型不匹配

// ❌ 错误:类型不匹配
const values: relationalStore.ValuesBucket = {
  name: 12345       // SQL 中 name 是 TEXT,但传了 number
}

// ✅ 正确:类型一致
const values: relationalStore.ValuesBucket = {
  name: '鹿鹿'      // string → TEXT
}

八、协议层映射总结

8.1 数据的完整生命周期

// ① ArkTS 页面得到用户输入
const capturedText = '今天醒得很早...'

// ② 构造 Entity 对象
const entity: HandwritingEntity = {
  archive_id: 1,
  source: 'camera',
  ocr_text: capturedText,
  created_at: Date.now()
}

// ③ Entity → ValuesBucket(DAO 层)
const values: relationalStore.ValuesBucket = { /* ... */ }

// ④ ValuesBucket → RDB 存储
const id = await store.insert('handwriting', values)

// ⑤ 数据库存储 → 界面呈现(翻到下一篇文章")
// store.query → ResultSet → Entity → UI

总结

本文深入解析了鸿蒙 RDB 中 ValuesBucket 的使用方法:

  1. 定义ValuesBucket = Record<string, number | string | boolean | Uint8Array | null>
  2. 映射模式:Entity 对象 → ValuesBucket,?? 操作符处理可选字段
  3. 字段默认值:外键未关联→0,文本不存在→'',数值不存在→0
  4. 返回值insert 返回自增主键 id,可立即用于关联表插入
  5. 事务优化:批量插入时使用显式事务提升性能
  6. 常见错误:字段名拼写、NOT NULL 约束、类型不匹配

下一篇文章将介绍 ResultSet 结果集遍历与解析——从数据库查询结果到 Entity 对象的完整转换。

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!


参考资源:

  • ValuesBucket API 参考
  • RdbStore.insert 方法
  • [HandwritingDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/HandwritingDao.ets)
  • [UserDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/UserDao.ets)
  • [ArchiveDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/ArchiveDao.ets)
  • [ReportDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/ReportDao.ets)
  • [RelationDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/RelationDao.ets)
  • 鸿蒙数据持久化指南
  • HarmonyOS 开发文档

七、ValuesBucket 常见问题与最佳实践

7.1 字段名大小写

ValuesBucket 的键名必须与数据库中的列名完全一致(区分大小写):

// ✅ 正确:与 CREATE TABLE 中的列名一致
values['user_id'] = data.userId
values['created_at'] = data.createdAt

// ❌ 错误:大小写不匹配
values['userId'] = data.userId    // 数据库中是 user_id
values['CreatedAt'] = data.createdAt  // 数据库中是 created_at

7.2 undefined vs null 的处理

ValuesBucket 不接受 undefined,可选字段必须显式设为 null

// ✅ 正确:可选字段用 null
values['partner_id'] = data.partnerId ?? null

// ❌ 错误:undefined 会导致插入异常
values['partner_id'] = data.partnerId  // 如果 partnerId 未定义会报错

7.3 数值类型选择

ArkTS 类型 ValuesBucket 赋值类型 数据库存储类型
number number INTEGER / REAL
string string TEXT
boolean number (0/1) INTEGER
object / array JSON.stringify(obj) TEXT
Date date.toISOString() TEXT

7.4 批量插入优化

使用事务批量插入比逐条插入性能提升显著:

// 批量插入 —— 使用事务
async function batchCreate(records: Handwriting[]): Promise<void> {
  const store = DatabaseService.getInstance().getStore()
  await store.beginTransaction()
  try {
    for (const record of records) {
      const values: relationalStore.ValuesBucket = {}
      values['id'] = record.id
      values['user_id'] = record.userId
      values['created_at'] = record.createdAt
      await store.insert('handwriting', values)
    }
    await store.commit()
  } catch (err) {
    await store.rollBack()
    throw err
  }
}

如果这篇文章对你有帮助,欢迎点赞👍、收藏⭐、关注🔔,你的支持是我持续创作的动力!

Logo

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

更多推荐