31 数据更新与级联操作:update、delete 与数据一致性

📅 2026/7/11 7:52:19
31 数据更新与级联操作:update、delete 与数据一致性
31 数据更新与级联操作update、delete 与数据一致性前言图31 数据更新与级联操作update、delete 与数据一致性 运行效果截图HarmonyOS NEXT在数据库操作中数据更新和删除是比插入更敏感的操作——一旦出错可能造成数据丢失。对于有外键关联的表还需要考虑级联操作删除主表记录时关联的子表记录应该如何处理本文以鹿鹿·笔迹心理分析项目中 ArchiveDao 的级联删除、HandwritingDao 的批量更新为例深入解析鸿蒙 RDB 中的 update、delete 方法使用和数据一致性维护策略。鸿蒙官方·RdbStore 更新删除developer.huawei.com项目源码仓库harmony-app GitHub图级联删除流程——删除 archive 时自动清理关联的 handwriting 和 report任一步骤异常删除 archive 请求ArchiveDao.delete开始事务删除关联 reportDELETE FROM report WHERE handwriting_id IN ...删除关联 handwritingDELETE FROM handwriting WHERE archive_id ?删除关联 relationDELETE FROM relation WHERE archive_id ?删除 archiveDELETE FROM archive WHERE id ?提交事务级联删除完成回滚事务一、update 更新操作1.1 基本语法awaitstore.update(values:ValuesBucket,predicates:RdbPredicates):Promisenumbervalues要更新的字段和值predicatesWHERE 条件返回值受影响的行数1.2 单字段更新// UserDao.updateName — 更新用户名staticasyncupdateName(id:number,name:string):Promisevoid{conststoreUserDao.getStore()constpredicatesnewrelationalStore.RdbPredicates(TABLE)predicates.equalTo(id,id)awaitstore.update({name}asrelationalStore.ValuesBucket,predicates)}1.3 多字段更新// RelationDao.updateMatchScore — 更新匹配度staticasyncupdateMatchScore(id:number,score:number):Promisevoid{conststoreRelationDao.getStore()constpredicatesnewrelationalStore.RdbPredicates(TABLE)predicates.equalTo(id,id)awaitstore.update({match_score:score}asrelationalStore.ValuesBucket,predicates)}1.4 原始 SQL 更新绕过 Predicates// ArchiveDao.incrementRecord — 使用 executeSql 执行复杂更新staticasyncincrementRecord(archiveId:number,recordTime:number):Promisevoid{conststoreArchiveDao.getStore()awaitstore.executeSql(UPDATE archive SET record_count record_count 1, last_record_at ? WHERE id ?,[recordTime,archiveId])}为什么要用 executeSqlstore.update()只能传入固定的ValuesBucket不支持record_count record_count 1这种字段自增操作。executeSql可以执行任意 SQL 语句。操作方法支持的更新类型固定值更新store.update(values, predicates)SET column ?字段自增store.executeSql(sql, args)SET column column 1条件计算store.executeSql(sql, args)SET column fn(column)二、delete 删除操作2.1 基本语法awaitstore.delete(predicates:RdbPredicates):Promisenumber2.2 单表删除// RelationDao.delete — 删除关系绑定staticasyncdelete(id:number):Promisevoid{conststoreRelationDao.getStore()constpredicatesnewrelationalStore.RdbPredicates(TABLE)predicates.equalTo(id,id)awaitstore.delete(predicates)}三、级联删除3.1 ArchiveDao 的级联删除鸿蒙 RDB不支持外键约束的级联删除CASCADE需要手动在业务层实现// ArchiveDao.delete — 级联删除档案及关联数据staticasyncdelete(id:number):Promisevoid{conststoreArchiveDao.getStore()// ① 删除档案本身constpredicatesnewrelationalStore.RdbPredicates(TABLE)predicates.equalTo(id,id)awaitstore.delete(predicates)// ② 级联清理handwriting 表中的关联记录 → archive_id 置为 0未归类consthwPredicatesnewrelationalStore.RdbPredicates(handwriting)hwPredicates.equalTo(archive_id,id)awaitstore.update({archive_id:0}asrelationalStore.ValuesBucket,hwPredicates)// ③ 级联清理report 表中的关联记录 → archive_id 置为 0constrptPredicatesnewrelationalStore.RdbPredicates(report)rptPredicates.equalTo(archive_id,id)awaitstore.update({archive_id:0}asrelationalStore.ValuesBucket,rptPredicates)}3.2 级联策略比较策略做法本项目选择理由置空SET NULL关联字段设为 NULL 或 0✅ 设置为archive_id 0笔迹数据有价值不应删除级联删除CASCADE一起删除❌丢失笔迹数据拒绝RESTRICT有子记录时不让删❌用户应该能删除档案忽略不做任何处理❌残留脏数据设计原则删除档案不删除笔迹和报告而是将它们标记为未归类。因为笔迹和分析结果是用户创建的宝贵内容删除档案只是改变了它们的组织方式。3.3 事务保护下的级联操作为了保证级联操作的原子性应该使用事务包裹// 改进版事务保护的级联删除staticasyncdelete(id:number):Promisevoid{conststoreArchiveDao.getStore()try{awaitstore.executeSql(BEGIN TRANSACTION)// 删除档案constpredicatesnewrelationalStore.RdbPredicates(TABLE)predicates.equalTo(id,id)awaitstore.delete(predicates)// 清理 handwritingconsthwPredicatesnewrelationalStore.RdbPredicates(handwriting)hwPredicates.equalTo(archive_id,id)awaitstore.update({archive_id:0},hwPredicates)// 清理 reportconstrptPredicatesnewrelationalStore.RdbPredicates(report)rptPredicates.equalTo(archive_id,id)awaitstore.update({archive_id:0},rptPredicates)awaitstore.executeSql(COMMIT)}catch(e){awaitstore.executeSql(ROLLBACK)// 部分失败时全部回滚throwe}}四、批量更新4.1 循环更新// HandwritingDao.batchUpdateArchive — 将多条笔迹移入指定档案staticasyncbatchUpdateArchive(ids:number[],archiveId:number):Promisevoid{conststoreHandwritingDao.getStore()for(constidofids){constpredicatesnewrelationalStore.RdbPredicates(TABLE)predicates.equalTo(id,id)awaitstore.update({archive_id:archiveId}asrelationalStore.ValuesBucket,predicates)}}4.2 批量更新的优化// 优化版使用 SQL IN 一次性更新staticasyncbatchUpdateArchive(ids:number[],archiveId:number):Promisevoid{conststoreHandwritingDao.getStore()constplaceholders:stringids.map(()?).join(,)awaitstore.executeSql(UPDATE${TABLE}SET archive_id ? WHERE id IN (${placeholders}),[archiveId,...ids])}性能对比方式SQL 执行次数适合的场景循环 update当前方式N 次每条一次少量10 条SQL IN 批量更新1 次批量10-100 条事务包裹的循环1 组事务需要原子性五、ArchiveDao.incrementRecord 模式5.1 原子自增staticasyncincrementRecord(archiveId:number,recordTime:number):Promisevoid{conststoreArchiveDao.getStore()awaitstore.executeSql(UPDATE archive SET record_count record_count 1, last_record_at ? WHERE id ?,[recordTime,archiveId])}为什么用 executeSql 而非 update// ❌ 无法用 update 实现// update 的 values 是固定值awaitstore.update({record_count:???,last_record_at:timestamp}asValuesBucket,predicates)// 问题我们不知道当前的 record_count 值// ✅ executeSql 直接操作数据库值record_count record_count 1// 数据库层面自增不需要先查后改5.2 批量增加staticasyncincrementRecordBy(archiveId:number,recordTime:number,count:number):Promisevoid{conststoreArchiveDao.getStore()awaitstore.executeSql(UPDATE archive SET record_count record_count ?, last_record_at ? WHERE id ?,[count,recordTime,archiveId])}六、更新/删除操作的数据一致性6.1 项目中维护一致性的方法一致性场景做法代码位置删除档案→笔迹标记未归类手动级联清理ArchiveDao.delete写入笔迹→档案计数incrementRecordDemoSeeder删除关系→档案不变不级联RelationDao.delete更新用户名→档案不更新不做级联合理UserDao.updateName6.2 一致性策略总结// 策略 1级联清理删除档案时ArchiveDao.delete(archiveId)→ handwriting.archive_id0→ report.archive_id0// 策略 2同步更新写入笔迹时HandwritingDao.create(handwriting)→ ArchiveDao.incrementRecord(archiveId,timestamp)// 手动增加档案的记录数// 策略 3不关联关系不影响业务数据RelationDao.delete(relationId)→ 不级联任何其他表// 解除关系不影响笔迹和报告七、安全的删除检查7.1 删前确认在 UI 层删除操作前应让用户确认// 弹窗确认删除AlertDialog.show({title:删除档案,message:删除后笔迹将变为未归类确定要删除吗,primaryButton:{value:取消,action:(){}},secondaryButton:{value:删除,fontColor:#E8919A,action:async(){awaitArchiveDao.delete(archiveId)// 刷新列表}}})7.2 异常回滚try{awaitArchiveDao.delete(id)// 更新 UI}catch(e){hilog.error(0x0000,ArchivePage,删除失败: %s,JSON.stringify(e))// 提示用户操作失败}总结本文深入解析了鸿蒙 RDB 中的更新和删除操作store.update(values, predicates)固定值更新适用于单/多字段修改store.delete(predicates)条件删除返回受影响行数级联删除实现鸿蒙不支持 CASCADE 外键约束需要手动实现级联操作原子自增store.executeSql(SET col col 1)实现字段自增事务保护多个写操作用BEGIN TRANSACTIONCOMMIT/ROLLBACK包裹数据一致性删除档案时笔迹标记为未归类而非删除保留用户数据下一篇文章将总结DAO 设计模式实战——项目中完整的 DAO 层设计。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力参考资源RdbStore.update 方法RdbStore.delete 方法[ArchiveDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/ArchiveDao.ets)[HandwritingDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/HandwritingDao.ets)[RelationDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/RelationDao.ets)[UserDao 项目源码](file:///Users/fiona/Downloads/bijixinli/harmony-app/entry/src/main/ets/features/data/UserDao.ets)SQLite 事务管理鸿蒙数据持久化指南HarmonyOS 开发文档七、软删除 vs 物理删除本项目使用物理删除直接从数据库中移除记录。根据业务需求有时软删除更合适策略实现方式优点缺点物理删除DELETE FROM table WHERE id ?简单数据库无冗余无法恢复级联复杂软删除UPDATE SET is_deleted 1 WHERE id ?可恢复保留历史查询时需过滤 is_deleted7.1 软删除实现示例// 软删除标记为已删除不从数据库移除asyncsoftDelete(id:string):Promisevoid{conststoreDatabaseService.getInstance().getStore()constprednewrelationalStore.RdbPredicates(archive)pred.equalTo(id,id)constvalues:relationalStore.ValuesBucket{}values[is_deleted]1values[deleted_at]newDate().toISOString()awaitstore.update(values,pred)}// 查询时自动过滤软删除记录asyncfindAll(userId:string):PromiseArchive[]{constprednewrelationalStore.RdbPredicates(archive)pred.equalTo(user_id,userId).equalTo(is_deleted,0)// 只查未删除的.orderByDesc(created_at)// ...}如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力