家庭管理(下)
303. 家庭解散:事务混合删除 + prepare/bindValue 踩坑
日期: 2026-05-20 关联开发任务: 阶段 3 任务 3.3 家庭解散 关联设计文档: 004.数据库设计文档 §五 外键级联策略、001.概要设计文档 混合删除策略
一、问题/需求
户主可解散家庭。设计文档规定了「混合删除策略」——历史业务数据软删除(is_deleted=1),临时数据物理删除(DELETE)。7 步操作必须在单个事务内完成,全部成功或全部回滚。
二、思路
- 事务包裹:
db.transaction() → 7 步 → db.commit() → 任一步失败 → db.rollback() - UI 二次确认:Dialog 模态弹窗,避免误操作
- 仅户主可用:解散按钮
visible: isOwner
三、踩坑:prepare/bindValue 参数不匹配
现象
UPDATE wishlist_items SET is_deleted=1 WHERE family_id=:fid
单参数单绑定,q.prepare(sql) + q.bindValue(":fid", val) 始终报错”参数数量不匹配”。尝试过 QMap、initializer_list< pair >、QStringLiteral、const 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 | ✅ |
| 解散后界面返回个人中心 | ✅ |
| 二次确认弹窗正常工作 | ✅ |