输入关键词开始搜索

家庭管理(下)

303. 家庭解散:事务混合删除 + prepare/bindValue 踩坑

日期: 2026-05-20 关联开发任务: 阶段 3 任务 3.3 家庭解散 关联设计文档: 004.数据库设计文档 §五 外键级联策略、001.概要设计文档 混合删除策略


一、问题/需求

户主可解散家庭。设计文档规定了「混合删除策略」——历史业务数据软删除(is_deleted=1),临时数据物理删除(DELETE)。7 步操作必须在单个事务内完成,全部成功或全部回滚。


二、思路

  1. 事务包裹db.transaction() → 7 步 → db.commit() → 任一步失败 → db.rollback()
  2. UI 二次确认:Dialog 模态弹窗,避免误操作
  3. 仅户主可用:解散按钮 visible: isOwner

三、踩坑:prepare/bindValue 参数不匹配

现象

UPDATE wishlist_items SET is_deleted=1 WHERE family_id=:fid

单参数单绑定,q.prepare(sql) + q.bindValue(":fid", val) 始终报错”参数数量不匹配”。尝试过 QMapinitializer_list< pair >QStringLiteralconst char* 四种传参方式均失败。

根因

Qt 6.9.0 + SQLite 驱动下,部分 SQL 语句(特别是含 SET is_deleted=1 的 UPDATE)在 prepare 阶段出现占位符识别异常。具体原因可能是 SQLite 将 is_deleted=1 中的 =1 误解析为命名参数前缀(:1 类似 PostgreSQL 的位置参数 $1),但 Qt 驱动层面未正确处理。

修复

放弃 prepare/bindValue,改用 QSqlQuery::exec() + QString::arg() 直接拼接:

auto run = [&](const QString &sql) -> bool {
    QSqlQuery q(db);
    if (!q.exec(sql)) { db.rollback(); return false; }
    return true;
};

run(QStringLiteral("UPDATE families SET is_deleted=1, updated_at='%1' WHERE id='%2'")
    .arg(now, familyId));

安全性familyId/now 均为系统生成的 UUID/时间戳,不含用户输入,无 SQL 注入风险。


四、附带修复:wishlist_items 无 is_deleted

数据库设计文档中 wishlist_items 表缺少 is_deleted 字段。解散时无法软删除,改为物理 DELETE


五、实施方案

级联删除顺序

BEGIN TRANSACTION
1. daily_menus          → soft delete (is_deleted=1)
2. menu_items           → soft delete (通过 menu_id 关联)
3. wishlist_items       → physical delete (无 is_deleted 字段)
4. inventory_batches    → soft delete
5. shopping_list_items  → physical delete
6. family_members       → physical delete
7. families             → soft delete
COMMIT

六、验证

验证项结果
解散后 daily_menus is_deleted=1
解散后 family_members 物理清除
解散后 families is_deleted=1
解散后界面返回个人中心
二次确认弹窗正常工作