You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

借助Mongo-Java-Driver原子更新Event文档数组字段并计算可用票数

原子更新DocumentDB中含计算逻辑的数组元素

场景说明

我正在处理event集合,原始文档结构如下:

{
  "event_name": "John Doe Concert",
  "event_date": "2022-01-01",
  "ticket_types": [
    {
      "name": "Front seats",
      "total": 50, // oldTotal1
      "available": 25 // oldAvailable1
    },
    {
      "name": "Back seats",
      "total": 100, // oldTotal2
      "available": 50 // oldAvailable2
    }
  ]
}

REST API的PUT端点接收如下请求体:

{
  "event_name": "Jane Doe Concert",
  "event_date": "2022-02-02",
  "ticket_types": [
    {
      "name": "Front seats",
      "total": 100 // newTotal1
    },
    {
      "name": "Back seats",
      "total": 150 // newTotal2
    }
  ]
}

需要将文档原子更新为以下结构(已知newTotal[i] >= oldTotal[i]):

{
  "event_name": "Jane Doe Concert",
  "event_date": "2022-02-02",
  "ticket_types": [
    {
      "name": "Front seats",
      "total": 100, // newTotal1
      "available": 75 // 计算逻辑:oldAvailable1 + newTotal1 - oldTotal1
    },
    {
      "name": "Back seats",
      "total": 150, // newTotal2
      "available": 100 // 计算逻辑:oldAvailable2 + newTotal2 - oldTotal2
    }
  ]
}

核心需求

  • 必须通过DocumentDB的findAndUpdate完成原子更新,禁止先查询文档再用Java代码修改的非原子操作
  • 运行环境为AWS Lambda,无法依赖外部锁机制,不使用事务API
  • 使用DocumentDB 4.0,应用层为Java

解决方案:使用更新聚合管道

普通的$set操作无法引用文档当前字段进行计算,而更新聚合管道支持表达式运算,能满足原子计算更新的需求。以下是具体实现:

JavaScript示例(Mongo Shell/DocumentDB CLI)

db.events.findOneAndUpdate(
  { event_name: "John Doe Concert" }, // 查询条件
  [
    {
      $set: {
        event_name: "Jane Doe Concert",
        event_date: "2022-02-02",
        ticket_types: {
          $map: {
            input: "$ticket_types",
            as: "ticket",
            in: {
              $switch: {
                branches: [
                  {
                    case: { $eq: ["$$ticket.name", "Front seats"] },
                    then: {
                      name: "$$ticket.name",
                      total: 100, // 对应请求体中的newTotal1
                      available: { $add: ["$$ticket.available", { $subtract: [100, "$$ticket.total"] }] }
                    }
                  },
                  {
                    case: { $eq: ["$$ticket.name", "Back seats"] },
                    then: {
                      name: "$$ticket.name",
                      total: 150, // 对应请求体中的newTotal2
                      available: { $add: ["$$ticket.available", { $subtract: [150, "$$ticket.total"] }] }
                    }
                  }
                ],
                default: "$$ticket" // 未匹配的票型保持原有结构
              }
            }
          }
        }
      }
    }
  ],
  { returnNewDocument: true } // 返回更新后的完整文档
)

Java代码示例

import com.mongodb.client.model.Aggregates;
import com.mongodb.client.model.Filters;
import com.mongodb.client.model.FindOneAndUpdateOptions;
import com.mongodb.client.model.ReturnDocument;
import org.bson.Document;
import org.bson.conversions.Bson;

import java.util.Arrays;

// 1. 构建查询条件(此处以event_name为例,可根据实际主键调整)
Bson query = Filters.eq("event_name", "John Doe Concert");

// 2. 构建更新聚合管道
Bson updatePipeline = Aggregates.set(
    "event_name", "Jane Doe Concert",
    "event_date", "2022-02-02",
    "ticket_types", new Document("$map", new Document("input", "$ticket_types")
        .append("as", "ticket")
        .append("in", new Document("$switch", new Document("branches", Arrays.asList(
            // 处理Front seats票型
            new Document("case", new Document("$eq", Arrays.asList("$$ticket.name", "Front seats")))
                .append("then", new Document("name", "$$ticket.name")
                    .append("total", 100) // 替换为请求体中的newTotal1
                    .append("available", new Document("$add", Arrays.asList(
                        "$$ticket.available",
                        new Document("$subtract", Arrays.asList(100, "$$ticket.total"))
                    ))),
            // 处理Back seats票型
            new Document("case", new Document("$eq", Arrays.asList("$$ticket.name", "Back seats")))
                .append("then", new Document("name", "$$ticket.name")
                    .append("total", 150) // 替换为请求体中的newTotal2
                    .append("available", new Document("$add", Arrays.asList(
                        "$$ticket.available",
                        new Document("$subtract", Arrays.asList(150, "$$ticket.total"))
                    )))
        ))
        .append("default", "$$ticket"))) // 其他票型保持不变
);

// 3. 设置更新选项:返回更新后的文档
FindOneAndUpdateOptions options = new FindOneAndUpdateOptions()
    .returnDocument(ReturnDocument.AFTER);

// 4. 执行原子更新并获取结果
Document updatedDocument = collection.findOneAndUpdate(query, updatePipeline, options);

逻辑说明

  • $map遍历ticket_types数组,对每个元素逐一处理
  • $switch根据票型名称匹配对应的更新规则,确保每个票型的total和available按需求更新
  • $add和$subtract组合实现available = 旧available值 + (新total值 - 旧total值)的计算逻辑
  • 整个操作是文档级原子操作,完全在DocumentDB端完成,符合Lambda环境的无锁、无事务需求

内容的提问来源于stack exchange,提问作者Lorenzo Valente

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 13:30:52