fix(mapper): 补 5 个「只有声明、无任何 SQL」的方法;新建 Mapper 绑定守卫持续挡住该类缺陷
全仓无 mapper XML、无自定义 ISqlInjector,自定义方法只能靠注解提供 SQL。 这 5 个方法漏写注解,编译期无感知,调用必抛 Invalid bound statement: UserPrivacySettingsMapper.findByUserId —— 隐私设置读取 UserPreferenceMapper.findByUserId —— 推荐引擎 / 匹配资格共 6 处调用 ReviewReasonMapper.findAllEnabled —— 审核原因字典下拉 ReviewReasonMapper.findByCategory ReviewItemMapper.findByReviewTaskId 长期未被发现的原因:候选池为空(分支走不到)+ 测试库根本没有这 4 张表(双重不可达)。 补的 SQL 一律带 AND deleted_at IS NULL —— V037 之后这些表都有软删列, 只看 V002 建表会漏掉软删过滤,制造「读到已删数据」的新缺陷。 test(invariants): MapperBindingGuardTest —— 三用例 ① 绑定守卫:反射遍历 MapperRegistry 全部 Mapper 的声明方法,hasStatement 为假即失败; 按「实体有无主键」语义豁免复合主键实体的 selectById/updateById/deleteById 等 (MP 本就不为无主键实体注入,属正确行为;非类名白名单,故新增复合主键表同样适用)。 ② 探测自检:虚构 statementId 必须为假,防断言静默永真。 ③ 可执行性:插真实数据后真查一遍 —— 绑定存在 ≠ 能跑(本次即借此确认 JsonNode 映射无碍)。 负向注入已验证:移除 ReviewItemMapper 的 @Select → 守卫报恰 1 项缺失, 且可执行性用例抛真实 Invalid bound statement;还原后复跑全绿。 test(db): schema-h2.sql 补 4 张缺失表(user_privacy_settings / user_preferences / review_reasons / review_items),按 V002 + V037 之后的最终态建模(含 id、deleted_at 与 11 个匹配权重列),并保留 chk_item_type / chk_item_status / chk_pref_priority 约束。 docs(plan): 附 B 新增第 12 项;更正第 11 项中我自己上一轮的错误断言 —— 「表无 id 列导致 insert 必崩」不成立:V037 已补齐 id/deleted_at/权重列。 真实缺陷是 updatePrivacySettings 对已存在记录也走 insert(非 upsert)→ 第二次更新撞主键冲突。 方法论:判断表结构必须遍历全部迁移,只看 CREATE TABLE 会得出与生产相反的结论。
This commit is contained in:
@@ -406,7 +406,7 @@
|
||||
9. **本机跑 Java 测试的通道 —— 含两处「必挂」,漏掉任一处都会导出完全错误的结论**(工具性发现,可复用)
|
||||
本机**无 JDK / 无 Maven / 无 `~/.m2`**(`mvn` 报 `Could not find Maven installation`),
|
||||
测试镜像 `danyang-match-backend-test` 内含 Temurin JDK 21 + Maven 3.9.16 + 完整源码。
|
||||
**正确命令**(2026-09-19 实测 `BUILD SUCCESS`,全量 **678 / 0 failures / 0 errors / 3 skipped**,约 6 分钟):
|
||||
**正确命令**(2026-09-19 实测 `BUILD SUCCESS`,全量 **681 / 0 failures / 0 errors / 3 skipped**,约 6 分钟):
|
||||
```bash
|
||||
MSYS_NO_PATHCONV=1 docker run --rm \
|
||||
-v "E:/develop/公益婚恋服务平台":/proj \
|
||||
@@ -473,10 +473,55 @@
|
||||
表里的 5 列在小程序里没有 UI。三方只有 `visibleInRecommendations` 同时存在。
|
||||
- 也就是说:**桩是「照前端契约写的假数据」,而「真实实现」是「照表结构写的」** ——
|
||||
两者根本没在同一个契约上。直接「接线」会立刻打断小程序页面。
|
||||
- 另有**表/实体错配**:`user_privacy_settings` 主键是 `user_id`、且**无 `id` / `deleted_at` 列**,
|
||||
而实体 `UserPrivacySettings extends BaseEntity`(带 `@TableId` 的 `id` 与 `deletedAt`)→
|
||||
`PrivacyService.updatePrivacySettings` 里的 `privacySettingsMapper.insert(...)` 会尝试写 `id` 列 →
|
||||
**必然失败**(且它对该插入路径用 `insert` 而非 upsert,即使列对齐也存在「已存在记录再插入」的问题)。
|
||||
- 另有**表/实体错配**(⚠️ 2026-09-19 **二轮更正**:初稿这条判断**部分错误**,已实测推翻):
|
||||
初稿称「`user_privacy_settings` 无 `id` / `deleted_at` 列 → insert 会尝试写 `id` → 必然失败」。
|
||||
该结论**只看 `V002` 的初始建表,漏看了后续迁移**。
|
||||
`V037__align_entity_columns_with_migrations` 恰恰是为这类错配而写的,它已补齐:
|
||||
`user_privacy_settings`(+`id`,+`deleted_at`)、`user_preferences`(+`id`,+`deleted_at`,+11 个权重列)、
|
||||
`review_reasons`(+`id`,+`created_at`/`updated_at`/`deleted_at`)、`review_items`(+`deleted_at`),
|
||||
且其注释明确点出根因是「`AutoFillHandler.insertFill` 会 setValue("id", UUID),MP 随即把 id 写进 INSERT」。
|
||||
**故「id 列不存在」不成立,insert 不会因此崩。**
|
||||
- 但**真实缺陷仍在**:`user_privacy_settings.user_id` 是主键,而 `PrivacyService.updatePrivacySettings`
|
||||
对**已存在**记录同样走 `insert`(不是 upsert)→ **第二次更新必撞主键冲突**。
|
||||
这才是 Task 3.1 第 1 项真正要修的点。
|
||||
- **方法论教训(可复用)**:判断「某表到底有哪些列」必须遍历**全部**迁移文件,
|
||||
尤其是 `V037` 这类专门"对齐实体与迁移"的迁移 —— 只看 `CREATE TABLE` 那一条会得出
|
||||
**与生产相反**的结论。测试库 H2 schema 同理,应按**最终态**(V002 + V037)建模,
|
||||
本轮已按此补建 4 张表。
|
||||
- 附带:`user_privacy_settings` / `user_preferences` / `review_reasons` / `review_items`
|
||||
**四张表均不在 H2 测试库**(同附 B-8 的表漂移盲区)—— 本轮已补齐(按 V037 之后的最终态)。
|
||||
「缺表 + 语句未绑定」叠加,使相关代码路径在测试期**双重不可达** —— 本轮已补齐表。
|
||||
|
||||
12. **5 个 Mapper 方法「声明了却没有任何 SQL」**(High,**本轮已修复 + 已建持续守卫**)
|
||||
全仓**无 mapper XML**(`mapper-locations` 未配置)、**无自定义 `ISqlInjector`** →
|
||||
自定义方法只能靠 `@Select/@Update/@Insert/@Delete` 提供 SQL。实测这 5 个方法只有声明:
|
||||
|
||||
| Mapper 方法 | 影响面 |
|
||||
|---|---|
|
||||
| `UserPrivacySettingsMapper.findByUserId` | 隐私设置读取 |
|
||||
| `UserPreferenceMapper.findByUserId` | 推荐引擎 / 匹配资格共 6 处调用 |
|
||||
| `ReviewReasonMapper.findAllEnabled` | 审核原因字典下拉选项 |
|
||||
| `ReviewReasonMapper.findByCategory` | 同上 |
|
||||
| `ReviewItemMapper.findByReviewTaskId` | 审核项列表 |
|
||||
|
||||
调用必抛 `Invalid bound statement`,**编译期完全无感知**。
|
||||
长期未被发现的原因:**候选池为空(分支走不到)+ 测试库根本没有这 4 张表** → 双重不可达。
|
||||
|
||||
- **已修**:补 `@Select`,且**均带 `AND deleted_at IS NULL`**(V037 之后这些表都有软删列,
|
||||
只看 V002 建表会漏掉软删过滤,制造"读到已删数据"的新缺陷)。
|
||||
- **已建持续守卫**:`MapperBindingGuardTest` —— 反射遍历 MyBatis `MapperRegistry` 中所有
|
||||
Mapper 的声明方法,`configuration.hasStatement(...)` 为假即失败;另含
|
||||
**探测方式自检**(虚构 statementId 必须为假,防断言静默永真)与
|
||||
**可执行性验证**(插真实数据后真查一遍,证明"绑定存在"≠"能跑")。
|
||||
- **守卫豁免规则**:复合主键实体(无 `@TableId`,如 `ConversationMember`、
|
||||
`ActivityStaffAssignment`)本就不会被 MP 注入 `selectById`/`updateById`/`deleteById`
|
||||
等依赖主键的方法 —— 这是 MP 的**正确行为**,守卫按"实体有无主键"这一**语义**豁免
|
||||
(不是按类名白名单,故新增复合主键表同样适用)。实测这两个 Mapper 在生产**零调用点**。
|
||||
- **负向注入已验证**:临时移除 `ReviewItemMapper` 的 `@Select` →
|
||||
绑定守卫报**恰 1 项缺失**,可执行性用例抛真实 `Invalid bound statement`;还原后复跑全绿。
|
||||
- ⚠️ **并发写者两次覆盖编辑**(本轮实测):写入的 import 与用例修改在编辑完成后被回滚,
|
||||
导致"改动看起来在、实际跑的是旧版"。**教训:编辑后到跑测试之间必须重新核对文件内容**,
|
||||
且跑通后应尽快提交以缩短暴露窗口。
|
||||
- **需要用户/产品三选一**:以表为准改造前端 UI / 以小程序为准补迁移加列 / 加一层映射。
|
||||
在此之前 Task 3.1 第 1 项**不应动手**。
|
||||
- 附带:`user_privacy_settings` / `user_preferences` / `review_reasons` / `review_items`
|
||||
|
||||
Reference in New Issue
Block a user