DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
RackNerd;$21.99/年 · 1GB KVM
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Panache 同时更新两个字段报 code 52,$set 里多出了 $and

Quarkus MongoDB Panache 的多字段更新可能把 and 解析为 $and 后包进 $set。用 Bson 更新定义保留字段和值,避免逗号写法静默漏更。

升级 Quarkus 后,单字段更新能用,下面的多字段 PanacheQL 更新却被 MongoDB 拒绝:

Person.update("firstname = ?1 and status = ?2", "b", "DEAD")
    .where("_id", id);

典型异常是 MongoWriteException,错误码 52,消息包含 The dollar ($) prefixed field '$and' ... is not allowed。这条回归从 3.37.0 的查询绑定变化引入,3.40.1 仍有相同路径;不能仅根据数据库提示里的 replacement document,就把普通字段更新改成整份文档替换。

过滤表达式被放进了更新文档

在这条路径里,and 被解析为 MongoDB 的逻辑过滤操作符 $and,而更新绑定器又把结果包进 $set。最终形状类似:

{"$set":{"$and":[{"firstname":"b"},{"status":"DEAD"}]}}

$and 可以组合查询条件,却不能这样成为 $set 要赋值的字段。使用命名参数也经过同一类绑定流程,换成 :firstname、:status 不能解决。

尤其不要把 and 随手换成逗号。受影响的解析入口可能只消费第一个谓词,留下的部分没有被执行,表现为请求成功但只改了第一个字段。对更新来说,这比明确异常更难发现。

使用原生 Bson 更新定义

对于已确认受影响的调用,可以绕开字符串更新绑定,改用 update(Bson)。以下是已有 Person Panache 实体对应的调用片段,字段确实以 firstname、status 保存,id 为已校验、属于当前操作范围的真实文档身份:

import static com.mongodb.client.model.Updates.combine;
import static com.mongodb.client.model.Updates.set;

long changed = Person.update(combine(
    set("firstname", "b"),
    set("status", "DEAD")
)).where("_id", id);

combine 组合两个字段赋值,产生合法的更新操作;where 继续限定目标文档。业务原本还依赖租户或其他授权条件时,应原样保留,不能为了示例简短把实际范围放宽。

Bson 中的字段名要按物理文档核对。实体如果通过 @BsonProperty 改了保存名称,不要假定字符串字段会自动套用实体映射。这个例子也只适用于两个字段都明确要求更新的请求;未提供的输入不能无条件改成 null。

同样可以构造正确的原生 $set 文档,但不要把用户输入拼入 JSON 字符串。驱动的更新构造器保留值与操作结构的区分,更容易检查哪些字段真正会被写入。

核对两个值,而不只看返回成功

截至 2026 年 10 月 23 日,3.40.1.1 的正式更新绑定器仍保留这一包装逻辑;不能把问题关闭或主分支处理当成该正式补丁版已经修复。

先在隔离集合检查两个字段都变化、值已相同、目标不存在和字段别名的情况,再按原部署流程替换调用。读取目标文档确认实际值,并确认其他字段与文档身份没有变化;返回的修改数量本身不能证明两个字段都已写入。

如果此前用逗号写法而静默漏更,恢复代码不会补齐历史数据。应依据业务记录找出缺失字段,单独审查修复范围与并发条件,不对整个集合补写一份固定值。